// // ebnaut-tx.c // // Copyright (c) 2015 Paul Nicholson // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions // are met: // 1. Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR // IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. // IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT // NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // // http://abelian.org/ebnaut/ #define VERSION "0.9b" #include #include #include #include #include #include #include #include #include #include #include #include static int QD = 0; // Number of output bits per input bit static int LB = 0; // Block size into convolutional encoder static int IW = 0; // Interleave stride static int Nsymbols = 0; // Number of output symbols (transmitted bits) static int port = 0; // Serial port number, 0 = COM1, 15 = COM16 static double sym_period = 10; static char *poly_string = "8K19A"; static time_t Tstart = 0; // Transmit start time static char Tstart_text[100]; #define MAX_CHARS 1000 // Maximum number of characters static char raw_message[MAX_CHARS+1]; // Message as entered by user static char clean_message[MAX_CHARS+1]; // Message purged of invalid chars #define MAX_BITS 6000 static uint8_t source_bits[MAX_BITS+1]; // Message after source encoding static int nmsg = 0; static char *sigbits = NULL; // The transmit bits, before interleaving static int QK = 0; // Encoder length: 1 + number of memory bits static int QP[16]; // Convolution polynomials, set by -p option static int repeat = 0; static int Wstate = 0; static time_t last_utc = 0; static HWND hwnd, hwnd_prep, hwnd_stop, hwnd_ok, hwnd_run, hwnd_info, hwnd_info_grp, hwnd_err, hwnd_utc, hwnd_comsel, hwnd_mc1, hwnd_mc2, hwnd_mc3, hwnd_codesel, hwnd_period, hwnd_save, hwnd_test, hwnd_msg, hwnd_msglen, hwnd_start, hwnd_st0, hwnd_st1, hwnd_st2, hwnd_st3, hwnd_rpt0, hwnd_rpt1, hwnd_rpt2, hwnd_rpt3, hwnd_crc; /////////////////////////////////////////////////////////////////////////////// // Polynomial Table // /////////////////////////////////////////////////////////////////////////////// struct POLYLIST { char *alias; char *poly; } polylist[] = { { "2K3A", "7,5" }, { "2K4A", "0xd,0xf" }, { "2K5A", "0x13,0x1d" }, { "2K6A", "0x2b,0x3d" }, { "2K7A", "0133,0171" }, { "2K8A", "0xa7,0xf9" }, { "2K9A", "0x131,0x1eb" }, { "2K10A", "0x277,0x365" }, { "2K11A", "0x4dd,0x7b1" }, { "2K12A", "04335,05723" }, { "2K13A", "010533,017661" }, { "2K13B", "011651,014677" }, { "2K14A", "021675,027123" }, { "2K14B", "026651,036477" }, { "2K15A", "047244,065231" }, { "2K15B", "046253,077361" }, { "2K16A", "0514576,0715022" }, { "2K16B", "0114727,0176121" }, { "2K17A", "0207225,0330747" }, { "2K17B", "0213631,0353323" }, { "2K18A", "0507517,0654315" }, { "2K21A", "05016515,06770547" }, { "2K23A", "051202215,066575563" }, { "3K3A", "5,7,7" }, { "3K4A", "013,015,017" }, { "3K5A", "025,033,037" }, { "3K6A", "047,053,075" }, { "3K7A", "0x5b,0x65,0x7d" }, { "3K8A", "0225,0331,0367" }, { "3K9A", "0557,0663,0711" }, { "3K10A", "01117,01365,01633" }, { "3K11A", "02353,02671,03175" }, { "3K12A", "04767,05723,06265" }, { "3K13A", "010533,010675,017661" }, { "3K14A", "021645,035661,037133" }, { "4K13A", "011145,012477,015537,016727" }, { "4K14A", "021113,023175,035527,035537" }, { "4K15A", "046321,051271,063667,070535" }, { "4K15B", "050575,051447,056533,066371" }, { "4K16A", "0123175,0135233,0156627,0176151" }, { "4K17A", "0235433,0247631,0264335,0311727" }, { "4K19A", "01122645,01373343,01531175,01634157" }, { "4K21A", "04542365,05342433,06347677,07232671" }, { "4K23A", "022346335,024275433,035520777,036271251" }, { "4K25A", "0106042635,0125445117,0152646773,0167561761" }, { "8K17A", "0222537,0226345,0255077,0267667,0306347,0315117,0326721," "0372751" }, { "8K19A", "01154451,01214561,01221517,01226537,01276775,01523533," "01543563,01632737" }, { "8K21A", "04470745,04714453,05175161,05366615,06375351,06752427," "06775363,07310533" }, { "8K23A", "023372665,024534765,025561775,027325743,031716223,032210543," "035451321,036744713" }, { "8K25A", "0105341521,0111747353,0114371121,0122355637,0134552773," "0150627331,0164507577,0172554315" }, { "16K19A", "01047113,01072363,01131677,01153145,01214573,01306665," "01334255,01346335,01456535,01555113,01676751,01723045," "01737471,01743251,01761527,01765237" }, { "16K21A", "04232547,04505631,05114365,05234555,05275577,05333523," "05634353,05663763,05735521,06110751,06273753,06523067," "06723455,07454677,07647511,07710617" }, { "16K23A", "021570463,022334751,023471145,023564713,024225255," "025336745,026522675,030653713,030664223,031367237," "032361377,033355313,036377267,037212147,037352421," "037553071" }, { "16K25A", "0104722251,0107672671,0122541663,0122545535,0123470447," "0125364663,0131553555,0131615665,0145114761,0145522105," "0146174323,0157705733,0162370765,0166576517,0167572771," "0176274217" }, { NULL, NULL } // Sentinel }; /////////////////////////////////////////////////////////////////////////////// // Utilities // /////////////////////////////////////////////////////////////////////////////// static void show_error( HWND hwnd, char *msg); static void bailout( char *format, ...) { va_list ap; char temp[ 200]; va_start( ap, format); vsprintf( temp, format, ap); va_end( ap); fprintf( stderr, "bailout: %s\n", temp); exit( 1); } static int atoin( char *s, int n) { int val = 0; while( n-- && isdigit( *s)) val = val * 10 + (*s++ - '0'); return val; } static void format_hhmmss( double secs, char *temp) { int isecs = (int) secs; sprintf( temp, "%02d:%02d:%05.2f", isecs/3600, (isecs % 3600)/60, secs - isecs/60 * 60 ); } static char *format_date( time_t when) { static char temp[100]; struct tm *tm = gmtime( &when); sprintf( temp, "%04d-%02d-%02d %02d:%02d:%02d", tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); return temp; } /////////////////////////////////////////////////////////////////////////////// // CRC // /////////////////////////////////////////////////////////////////////////////// static int CRCLEN = 16; // http://users.ece.cmu.edu/~koopman/crc/ // http://users.ece.cmu.edu/~koopman/crc/hw_data.html #define CRCBASE 0 // The CRC in the first entry of crclist[] #define CRCMAX 32 // Max CRC in crclist[] static uint64_t crclist[] = { 0x1, // 0 0x3, // 1 0x7, // 2 0xb, // 3 CRC-3K 0x13, // 4 CCITT-4 0x2b, // 5 CRC-5-ITU 0x47, // 6 CRC-6-CDMA2000-B 0xe5, // 7 CRC-7-MVB 0x137, // 8 CRC-8F/6 0x279, // 9 CRC-9F/6.2 0x537, // 10 CRC-10F/7 0x9eb, // 11 CRC-11F/8 0x149f, // 12 CRC-12F/8.2 0x216f, // 13 CRC-10F/8.2 0x46e3, // 14 CRC-14F/8.2 0xb48f, // 15 CRC-15K/9 0x11021, // 16 CCITT-16 0x2ed4f, // 17 CRC-17K/10 0x4d47b, // 18 CRC-18K/11 0xa3af3, // 19 CRC-19K/12 0x13ab0f, // 20 CRC-20K/12 0x2caea3, // 21 CRC-21K/12 0x5b9d23, // 22 CRC-22K/13 0x96f3a3, // 23 CRC-23K/14 0x16e7d23, // 24 CRC-24K/14 0x252399f, // 25 CRC-25K/14 0x4a3ecd7, // 26 CRC-26K/16 0xcb7aa27, // 27 0x19e2372b, // 28 0x2bc2cb4d, // 29 0x453be359, // 30 0x8dcad4f9, // 31 0x1741b8cd7 // 32 CRC-32K/6.1 }; // // 'data' and 'crc' are arrays containing one bit per byte. // static void make_crc( uint8_t *data, int len, uint8_t *crc) { if( !CRCLEN) return; uint8_t poly[33]; memset( poly, 0, 33); uint64_t c = crclist[CRCLEN - CRCBASE]; for( int i = 0; i <= CRCLEN; i++) poly[i] = c & (1 << (CRCLEN - i)) ? 1 : 0; uint8_t *buf = malloc( len + CRCLEN); memcpy( buf, data, len); memset( buf + len, 0, CRCLEN); for( int i = 0; i < len; i++) { if( !buf[i]) continue; for( int j = 0; j <= CRCLEN; j++) buf[i+j] ^= poly[j]; } memcpy( crc, buf + len, CRCLEN); free( buf); } // // * (dec 42) to ; (dec 59) coded as 0 to 17 // ! (dec 33) coded to 18 // = (dec 61) coded to 19 // & (dec 38) coded to 20 // ? (dec 63) to Z (dec 90) coded as 21 to 48 // '\n' coded as 49 but decoded to a space // space and tab both coded to 50 // underscore codes as 51 // static int encode_char( char c, int *nt) { int u = -1; c = toupper( c); if( c >= 42 && c <= 59) u = c - 42; else if( c >= 63 && c <= 90) u = c - 42; else switch( c) { case '!': u = 18; break; case '=': u = 19; break; case '&': u = 20; break; case '\n': u = 49; break; case ' ': case '\t': u = 50; break; case '_': u = 51; break; } if( nt && u >= 0) // A valid character to encode? { clean_message[nmsg++] = c; clean_message[nmsg] = 0; for( int i = 0; i < 6; i++) source_bits[*nt + i] = u & (1 << i) ? 1 : 0; *nt += 6; } return u; } static void parse_polys( void) { char *p = poly_string, *q; struct POLYLIST *list = polylist; while( list->alias && strcasecmp( list->alias, poly_string)) list++; if( list->alias) p = list->poly; QD = 0; QK = 0; while( 1) { uint32_t poly = (uint32_t) strtol( p, &q, 0); if( p == q) break; QP[QD++] = poly; int i = 0; do { poly >>= 1; i++; } while( poly != 0); if( i > QK) QK = i; p = q; if( *p == 0) break; if( *p != ',') bailout( "error parsing polynomial set\n"); p++; } } static uint32_t encstate; // Current encoder state static uint32_t outword = 0; static inline void reset_encoder( uint32_t val) { encstate = val; } static void encode( int b) { outword = 0; int i; encstate |= b << (QK-1); for( i=0; i>= 1; } static int interleave( int in, int N, int W) { if( in >= N) bailout( "interleave input out of range"); int H = N / W; // Number of rows int L = N % W; if( L) H++; // Extra incomplete row of length L int x = in % W; int y = in / W; if( !L || x < L) return x * H + y; return L * H + (x - L) * (H-1) + y; } /////////////////////////////////////////////////////////////////////////////// // RS232 Port // /////////////////////////////////////////////////////////////////////////////// HANDLE com_handle = INVALID_HANDLE_VALUE; // omode defines the use of the RTS signal static int omode = -1; #define OMODE_RTSPOS 0 // BPSK same polarity as DTR #define OMODE_RTSNEG 1 // BPSK opposite polarity to DTR #define OMODE_RTSPTT 2 // RTS controls the PTT static void set_bpsk_output( int val) { if( !val) { EscapeCommFunction( com_handle, CLRDTR); switch( omode) { case OMODE_RTSPOS: EscapeCommFunction( com_handle, CLRRTS); break; case OMODE_RTSNEG: EscapeCommFunction( com_handle, SETRTS); break; } } else { EscapeCommFunction( com_handle, SETDTR); switch( omode) { case OMODE_RTSPOS: EscapeCommFunction( com_handle, SETRTS); break; case OMODE_RTSNEG: EscapeCommFunction( com_handle, CLRRTS); break; } } } static void set_ptt_output( int state) { if( omode != OMODE_RTSPTT) return; EscapeCommFunction( com_handle, state ? SETRTS : CLRRTS); } static int setup_serial_port( void) { char temp[200]; if( com_handle != INVALID_HANDLE_VALUE) { CloseHandle( com_handle); com_handle = INVALID_HANDLE_VALUE; } int len = GetWindowTextLength( hwnd_comsel); GetWindowText( hwnd_comsel, temp, len+1); if( !strncasecmp( temp, "COM", 3)) port = atoi( temp+3) - 1; else { show_error( hwnd, "Invalid COM port"); port = -1; return FALSE; } if( port < 0 || port > 15) { show_error( hwnd, "Invalid COM port"); port = -1; return FALSE; } if( omode < 0) { show_error( hwnd, "Signalling mode not set"); return FALSE; } char comname[50]; sprintf( comname, "\\\\.\\COM%d", port+1); com_handle = CreateFileA( comname, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); if( com_handle == INVALID_HANDLE_VALUE) { show_error( hwnd, "Cannot open serial port"); return FALSE; } DCB dcb; memset( &dcb, 0, sizeof( dcb)); dcb.DCBlength = sizeof( dcb); COMMTIMEOUTS ct; memset( &ct, 0, sizeof( ct)); ct.ReadIntervalTimeout = MAXDWORD; if( !BuildCommDCBA( "baud=9600 data=8 parity=n stop=1 dtr=on rts=on", &dcb) || !SetCommState( com_handle, &dcb) || !SetCommTimeouts( com_handle, &ct) ) { show_error( hwnd, "Cannot open serial port"); return FALSE; } set_bpsk_output( 0); set_ptt_output( 0); return TRUE; } #define RTOP 395 static void show_error( HWND hwnd, char *msg) { char temp[200]; sprintf( temp, "ERROR: %s", msg); hwnd_err = CreateWindow( "STATIC", temp, WS_CHILD | WS_VISIBLE | SS_LEFT, 160, RTOP, 300, 20, hwnd, (HMENU) 1, NULL, NULL); } static int prepare_msg( void) { if( !poly_string) { show_error( hwnd, "Coding not selected"); return FALSE; } parse_polys(); if( !raw_message[0]) { show_error( hwnd, "No message given"); return FALSE; } int n = strlen( raw_message); if( n * 6 + CRCLEN > MAX_BITS) { show_error( hwnd, "Message too long"); return FALSE; } nmsg = 0; clean_message[0] = 0; int i, nt = 0; for( i=0; i MAX_CHARS) { show_error( hwnd, "Message too long"); return FALSE; } Nsymbols = QD * (LB + QK - 1); IW = ceil( sqrt( Nsymbols)); sigbits = (char *) realloc( sigbits, Nsymbols); if( !sigbits) { show_error( hwnd, "Not enough memory"); return FALSE; } reset_encoder( 0); int txbitcnt = 0; for( int i = 0; i < LB; i++) // Message bits { encode( source_bits[i]); for( int j = QD-1; j >= 0; j--) { int bit = outword & (1<= 0; j--) { int bit = outword & (1<tm_year; i++) t += !is_leapyear( i) ? 31536000L : 31622400L; for( int i = 0; i < tm->tm_mon; i++) t += mdays[i] * 86400L; if( is_leapyear( tm->tm_year) && tm->tm_mon > 1) t += 86400L; for( int i = 1; i < tm->tm_mday; i++) t += 86400L; return t + tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec; } static void prepare( HWND hwnd) { char temp[50]; clear_error(); if( !prepare_msg()) return; if( !setup_serial_port()) return; int len = GetWindowTextLength( hwnd_period); if( len) { GetWindowText( hwnd_period, temp, len + 1); sym_period = atof( temp); } if( !len || sym_period < 0.01 || sym_period > 120) { show_error( hwnd, "Invalid symbol period"); return; } struct tm tm; memset( &tm, 0, sizeof( struct tm)); // yyyy-mm-dd hh:mm:ss // 0123456789012345678 if( !Tstart_text[0]) { show_error( hwnd, "Needs start time"); return; } if( strlen( Tstart_text) != 19 || !isdigit( Tstart_text[0]) || !isdigit( Tstart_text[1]) || !isdigit( Tstart_text[2]) || !isdigit( Tstart_text[3]) || (Tstart_text[4] != '-' && Tstart_text[4] != '/') || !isdigit( Tstart_text[5]) || !isdigit( Tstart_text[6]) || (Tstart_text[10] != ' ' && Tstart_text[10] != '_') || !isdigit( Tstart_text[11]) || !isdigit( Tstart_text[12]) || Tstart_text[13] != ':' || !isdigit( Tstart_text[14]) || !isdigit( Tstart_text[15]) || Tstart_text[16] != ':' || !isdigit( Tstart_text[17]) || !isdigit( Tstart_text[18])) { show_error( hwnd, "Invalid start time"); return; } tm.tm_year = atoin( Tstart_text, 4) - 1900; tm.tm_mon = atoin( Tstart_text+5, 2) - 1; tm.tm_mday = atoin( Tstart_text+8, 2); tm.tm_hour = atoin( Tstart_text+11, 2); tm.tm_min = atoin( Tstart_text+14, 2); tm.tm_sec = atoin( Tstart_text+17, 2); Tstart = timegm( &tm); struct timeval tv; gettimeofday( &tv, NULL); int j = Tstart - tv.tv_sec; printf( "Tstart %ld utc %ld\n", Tstart, tv.tv_sec); if( j <= 0) { show_error( hwnd, "Start time already gone"); return; } double duration = Nsymbols * sym_period; format_hhmmss( duration, temp); char info_text[1000]; sprintf( info_text, "Message text: %s\n" "Characters: %d, " "Number of signal bits: %d\n" "Duration: %s\n", clean_message, nmsg, Nsymbols, temp); DestroyWindow( hwnd_prep); DestroyWindow( hwnd_save); DestroyWindow( hwnd_test); hwnd_info_grp = CreateWindow( "button", "Transmission Info", WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 10, RTOP, 425, 120, hwnd, (HMENU) 0, NULL, NULL); hwnd_info = CreateWindow( "STATIC", info_text, WS_CHILD | WS_VISIBLE | SS_LEFT, 20, RTOP+20, 400, 80, hwnd, (HMENU) 1, NULL, NULL); hwnd_run = CreateWindow( "STATIC", "Run", WS_CHILD | WS_VISIBLE | SS_LEFT, 150, RTOP+143, 300, 30, hwnd, (HMENU) 1, NULL, NULL); enable_setup( FALSE); show_stop(); Wstate = 1; } static void test( HWND hwnd) { int len; char temp[50]; clear_error(); if( !setup_serial_port()) return; len = GetWindowTextLength( hwnd_period); if( len) { GetWindowText( hwnd_period, temp, len + 1); sym_period = atof( temp); } if( !len || sym_period < 0.1 || sym_period > 120) { show_error( hwnd, "Invalid symbol period"); return; } struct timeval tv; gettimeofday( &tv, NULL); Tstart = tv.tv_sec; DestroyWindow( hwnd_prep); DestroyWindow( hwnd_save); DestroyWindow( hwnd_test); hwnd_info_grp = CreateWindow( "button", "Transmission Info", WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 10, RTOP, 425, 120, hwnd, (HMENU) 0, NULL, NULL); hwnd_info = CreateWindow( "STATIC", "Relay test running", WS_CHILD | WS_VISIBLE | SS_LEFT, 20, RTOP+20, 390, 80, hwnd, (HMENU) 1, NULL, NULL); hwnd_run = CreateWindow( "STATIC", "Run", WS_CHILD | WS_VISIBLE | SS_LEFT, 150, RTOP+143, 300, 30, hwnd, (HMENU) 1, NULL, NULL); show_stop(); enable_setup( FALSE); Wstate = 10; } // // Save the encoded bits, in transmit order, into a text file. // static void save_text( HWND hwnd, int rows) { clear_error(); if( !prepare_msg()) return; char filename[MAX_PATH]; OPENFILENAME ofn; ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.lpstrFile = filename; ofn.lpstrFile[0] = '\0'; ofn.hwndOwner = hwnd; ofn.nMaxFile = sizeof( filename); ofn.lpstrFilter = TEXT("All files(*.*)\0*.*\0"); ofn.nFilterIndex = 1; ofn.lpstrInitialDir = NULL; ofn.lpstrFileTitle = NULL; ofn.Flags = OFN_PATHMUSTEXIST; if( GetSaveFileName( &ofn)) { FILE *f = fopen( filename, "wt"); if( !f) { show_error( hwnd, "Cannot open file to save"); return; } for( int i = 0; i < Nsymbols; i++) { fprintf( f, "%d", sigbits[interleave( i, Nsymbols, IW)]); if( rows) fprintf( f, "\n"); } if( !rows) fprintf( f, "\n"); fclose( f); } } // // Called every 20mS. // static void handle_timer( __attribute__ ((unused)) HWND hwnd) { static int cbit = -1; struct timeval tv; gettimeofday( &tv, NULL); // // Update the UTC display. // if( tv.tv_sec != last_utc) { last_utc = tv.tv_sec; char temp[100]; sprintf( temp, "Current UTC: %s", format_date( last_utc)); SetWindowText( hwnd_utc, temp); } // // State 0: idle state. // if( Wstate == 0) return; // // State 1: waiting for start time to arrive. // if( Wstate == 1) { long double Tnow = tv.tv_sec + tv.tv_usec/1e6; double remains = Tstart - Tnow; if( remains > 0) { char temp[50], message[200]; format_hhmmss( remains, temp); sprintf( message, "Transmit starts in: %s", temp); SetWindowText( hwnd_run, message); set_ptt_output( remains > 2 ? 0 : 1); return; } cbit = -1; Wstate = 2; set_ptt_output( 1); } // // State 2: sending. // if( Wstate == 2) { long double elapsed = tv.tv_sec + tv.tv_usec/1e6 - Tstart; int bit = elapsed/sym_period; // Current bit number, 0 .. Nsymbols - 1 if( bit >= Nsymbols) // Send completed? { set_bpsk_output( 0); if( repeat) // Repeat requested? If so, prepare the next start time { uint64_t now = tv.tv_sec; if( tv.tv_usec/1e6 > 0.5) now++; Tstart = (now / repeat + 1) * repeat; Wstate = 1; return; } set_ptt_output( 0); Wstate = 3; SetWindowText( hwnd_run, "Transmit completed"); DestroyWindow( hwnd_stop); show_ok(); return; } if( bit != cbit) // Time to send next output bit? { cbit = bit; int val = sigbits[interleave( cbit, Nsymbols, IW)]; set_bpsk_output( val); char message[200]; sprintf( message, "Sending bit %d/%d state=%d\r", cbit+1, Nsymbols, val); SetWindowText( hwnd_run, message); } } // // State 3: sending complete, waiting for user to acknowledge. // if( Wstate == 3) return; // // State 10: test mode, toggle the output every sym_period. // if( Wstate == 10) { long double elapsed = tv.tv_sec + tv.tv_usec/1e6 - Tstart; int bit = elapsed/sym_period; if( bit != cbit) { set_ptt_output( 1); cbit = bit; int val = bit & 1; set_bpsk_output( val); char message[200]; sprintf( message, "Relay state %s\r", val ? "ON" : "OFF"); SetWindowText( hwnd_run, message); } } } // // IDs for the various display widgets. // #define ID_MSG 4 #define ID_START 7 #define ID_PERIOD 8 #define ID_TIMER 9 #define ID_PREPARE 10 #define ID_STOP 11 #define ID_OK 12 #define ID_COMSEL 20 #define ID_CODESEL 21 #define ID_MC1 22 #define ID_MC2 23 #define ID_MC3 24 #define ID_CRC 25 #define ID_ST1 30 #define ID_ST2 31 #define ID_ST3 32 #define ID_ST4 33 #define ID_SAVE1 51 #define ID_SAVE2 52 #define ID_TEST 61 #define ID_RPT0 80 #define ID_RPT1 81 #define ID_RPT2 82 #define ID_RPT3 83 static void show_prepare( void) { hwnd_prep = CreateWindow("button", "Prepare", WS_VISIBLE | WS_CHILD , 20, RTOP, 120, 25, hwnd, (HMENU) ID_PREPARE, NULL, NULL); hwnd_save = CreateWindow("button", "Save Line", WS_VISIBLE | WS_CHILD , 20, RTOP+40, 120, 25, hwnd, (HMENU) ID_SAVE1, NULL, NULL); hwnd_test = CreateWindow("button", "Save Rows", WS_VISIBLE | WS_CHILD , 20, RTOP+80, 120, 25, hwnd, (HMENU) ID_SAVE2, NULL, NULL); hwnd_test = CreateWindow("button", "Test", WS_VISIBLE | WS_CHILD , 20, RTOP+120, 120, 25, hwnd, (HMENU) ID_TEST, NULL, NULL); enable_setup( TRUE); } static void show_stop( void) { hwnd_stop = CreateWindow("button", "Stop", WS_VISIBLE | WS_CHILD , 20, RTOP + 140, 80, 25, hwnd, (HMENU) ID_STOP, NULL, NULL); } static void show_ok( void) { hwnd_ok = CreateWindow("button", "OK", WS_VISIBLE | WS_CHILD , 20, RTOP + 140, 80, 25, hwnd, (HMENU) ID_STOP, NULL, NULL); } static const TCHAR *comlist[] = { "COM 1", "COM 2", "COM 3", "COM 4", "COM 5", "COM 6", "COM 7", "COM 8", "COM 9", "COM 10", "COM 11", "COM 12", "COM 13", "COM 14", "COM 15", "COM 16" }; WNDPROC save_msgproc; // // Main window event handler. // static void setup_display( void); LRESULT CALLBACK main_event(HWND h, UINT msg, WPARAM wParam, LPARAM lParam) { int len; time_t t; switch( msg) { case WM_CREATE: hwnd = h; setup_display(); return 0; case WM_COMMAND: switch( LOWORD(wParam)) { case ID_MSG: len = GetWindowTextLength( hwnd_msg); if( len) { GetWindowText( hwnd_msg, raw_message, len + 1); char temp[200]; if( len > MAX_CHARS) sprintf( temp, "(length %d, too long, max %d)", len, MAX_CHARS); else sprintf( temp, "(length %d)", len); SetWindowText( hwnd_msglen, temp); } else { raw_message[0] = 0; SetWindowText( hwnd_msglen, ""); } return 0; case ID_START: len = GetWindowTextLength( hwnd_start); GetWindowText( hwnd_start, Tstart_text, len+1); return 0; case ID_COMSEL: if( HIWORD(wParam) == CBN_SELCHANGE) { int i = SendMessage( hwnd_comsel, CB_GETCURSEL, 0, 0); SetWindowText( hwnd_comsel, comlist[i]); } return 0; case ID_CODESEL: if( HIWORD(wParam) == CBN_SELCHANGE) { int i = SendMessage( hwnd_codesel, CB_GETCURSEL, 0, 0); poly_string = polylist[i].alias; SetWindowText( hwnd_codesel, poly_string); } return 0; case ID_CRC: if( HIWORD(wParam) == CBN_SELCHANGE) { int i = SendMessage( hwnd_crc, CB_GETCURSEL, 0, 0); CRCLEN = CRCBASE + i; } return 0; } if( HIWORD(wParam) == BN_CLICKED) switch( LOWORD(wParam)) { case ID_MC1: omode = OMODE_RTSPOS; return 0; case ID_MC2: omode = OMODE_RTSNEG; return 0; case ID_MC3: omode = OMODE_RTSPTT; return 0; case ID_PREPARE: prepare( hwnd); return 0; case ID_TEST: test( hwnd); return 0; case ID_SAVE1: enable_setup( FALSE); save_text( hwnd, FALSE); enable_setup( TRUE); return 0; case ID_SAVE2: enable_setup( FALSE); save_text( hwnd, TRUE); enable_setup( TRUE); return 0; case ID_STOP: set_bpsk_output( 0); set_ptt_output( 0); DestroyWindow( hwnd_info); DestroyWindow( hwnd_info_grp); DestroyWindow( hwnd_stop); DestroyWindow( hwnd_ok); DestroyWindow( hwnd_run); show_prepare(); Wstate = 0; return 0; case ID_RPT0: repeat = 0; return 0; case ID_RPT1: repeat = 600; return 0; case ID_RPT2: repeat = 1800; return 0; case ID_RPT3: repeat = 3600; return 0; case ID_ST1: t = (last_utc / 60 + 1) * 60; SetWindowText( hwnd_start, format_date( t)); return 0; case ID_ST2: t = (last_utc / 300 + 1) * 300; SetWindowText( hwnd_start, format_date( t)); return 0; case ID_ST3: t = (last_utc / 1800 + 1) * 1800; SetWindowText( hwnd_start, format_date( t)); return 0; case ID_ST4: t = (last_utc / 3600 + 1) * 3600; SetWindowText( hwnd_start, format_date( t)); return 0; } break; case WM_TIMER: handle_timer( h); return 0; case WM_DESTROY: PostQuitMessage( 0); return 0; } return DefWindowProc( h, msg, wParam, lParam); } LRESULT CALLBACK msg_edit (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { char c; switch( message) { case WM_CHAR: c = (char) wParam; if( c != 8 && encode_char( c, NULL) < 0) return 0; break; } return CallWindowProc (save_msgproc, hwnd, message, wParam, lParam); } // // Once-only initialisation of the main window. // static void setup_display( void) { int Y = 10; // // Code selection. // CreateWindow( "button", "Select Coding", WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 10, Y, 425, 65, hwnd, (HMENU) 1, NULL, NULL); hwnd_codesel = CreateWindow( "combobox", NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST, 20, Y+20, 130, 80, hwnd, (HMENU) ID_CODESEL, NULL, NULL); for( struct POLYLIST *p = polylist; p->alias; p++) SendMessage( hwnd_codesel, CB_ADDSTRING, 0, (LPARAM) p->alias); for( struct POLYLIST *p = polylist; p->alias; p++) if( !strcmp( p->alias, poly_string)) SendMessage( hwnd_codesel, CB_SETCURSEL, (WPARAM) (p - polylist), (LPARAM) 0); // // CRC length. // char temp[100]; hwnd_crc = CreateWindow( "combobox", NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST, 160, Y+20, 80, 80, hwnd, (HMENU) ID_CRC, NULL, NULL); for( int i = CRCBASE; i <= CRCMAX; i++) { sprintf( temp, "CRC %d", i); SendMessage( hwnd_crc, CB_ADDSTRING, 0, (LPARAM) temp); } SendMessage( hwnd_crc, CB_SETCURSEL, (WPARAM) (CRCLEN - CRCBASE), (LPARAM) 0); // // Symbol period. // CreateWindow( "STATIC", "Symbol period:", WS_CHILD | WS_VISIBLE | SS_LEFT, 260, Y + 23, 100, 20, hwnd, (HMENU) 1, NULL, NULL); hwnd_period = CreateWindow( "Edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER, 370, Y + 20, 40, 20, hwnd, (HMENU) ID_PERIOD, NULL, NULL); sprintf( temp, "%.1f", sym_period); SetWindowText( hwnd_period, temp); Y += 80; CreateWindow( "button", "Serial Port", WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 10, Y, 425, 75, hwnd, (HMENU) 0, NULL, NULL); hwnd_comsel = CreateWindow( "combobox", NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | CBS_DROPDOWNLIST | WS_OVERLAPPED, 20, Y+30, 120, 80, hwnd, (HMENU) ID_COMSEL, NULL, NULL); hwnd_mc1 = CreateWindow( "button", "DTR + RTS", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON | WS_GROUP, 160, Y+15, 120, 30, hwnd, (HMENU)ID_MC1, NULL, NULL); hwnd_mc2 = CreateWindow( "button", "DTR - RTS", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 310, Y+15, 120, 30, hwnd, (HMENU)ID_MC2, NULL, NULL); hwnd_mc3 = CreateWindow( "button", "DTR, RTS=PTT", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 160, Y+40, 120, 30, hwnd, (HMENU)ID_MC3, NULL, NULL); for ( int i = 0; i < 16; i++ ) SendMessage( hwnd_comsel, CB_ADDSTRING, 0, (LPARAM) comlist[i]); SendMessage( hwnd_comsel, CB_SETCURSEL, (WPARAM)0, (LPARAM)0); Y += 60; CreateWindow( "STATIC", "Message:", WS_CHILD | WS_VISIBLE | SS_LEFT, 20, Y+16, 100, 20, hwnd, (HMENU) 1, NULL, NULL); hwnd_msglen = CreateWindow( "STATIC", "", WS_CHILD | WS_VISIBLE | SS_LEFT, 130, Y+16, 250, 20, hwnd, (HMENU) 1, NULL, NULL); hwnd_msg = CreateWindow( "Edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL, 10, Y+40, 425, 20, hwnd, (HMENU) ID_MSG, NULL, NULL); save_msgproc = (WNDPROC) SetWindowLong( hwnd_msg, GWL_WNDPROC, (LONG)msg_edit); Y += 75; CreateWindow( "button", "Start Time", WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 10, Y, 425, 155, hwnd, (HMENU) 0, NULL, NULL); hwnd_utc = CreateWindow( "STATIC", "", WS_CHILD | WS_VISIBLE | SS_LEFT, 20, Y+23, 280, 20, hwnd, (HMENU) 1, NULL, NULL); CreateWindow( "STATIC", "UTC YYYY-MM-DD HH:MM:SS:", WS_CHILD | WS_VISIBLE | SS_LEFT, 20, Y+50, 280, 20, hwnd, (HMENU) 1, NULL, NULL); hwnd_start = CreateWindow( "Edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER, 240, Y+50, 170, 20, hwnd, (HMENU) ID_START, NULL, NULL); hwnd_st0 = CreateWindow("button", "Next Min", WS_VISIBLE | WS_CHILD , 20, Y+80, 80, 25, hwnd, (HMENU) ID_ST1, NULL, NULL); hwnd_st1 = CreateWindow("button", "Next 5 Mins", WS_VISIBLE | WS_CHILD , 110, Y+80, 100, 25, hwnd, (HMENU) ID_ST2, NULL, NULL); hwnd_st2 = CreateWindow("button", "Next 30 Mins", WS_VISIBLE | WS_CHILD , 220, Y+80, 110, 25, hwnd, (HMENU) ID_ST3, NULL, NULL); hwnd_st3 = CreateWindow("button", "Next Hour", WS_VISIBLE | WS_CHILD , 340, Y+80, 80, 25, hwnd, (HMENU) ID_ST4, NULL, NULL); hwnd_rpt0 =CreateWindow( "button", "No repeat", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON | WS_GROUP, 20, Y+115, 90, 30, hwnd, (HMENU)ID_RPT0 , NULL, NULL); SendMessage( hwnd_rpt0, BM_SETCHECK, 1,0); hwnd_rpt1 = CreateWindow( "button", "On 10 min", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 20+105, Y+115, 90, 30, hwnd, (HMENU)ID_RPT1 , NULL, NULL); hwnd_rpt2 = CreateWindow( "button", "On 30 min", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 20+210, Y+115, 90, 30, hwnd, (HMENU)ID_RPT2 , NULL, NULL); hwnd_rpt3 = CreateWindow( "button", "On hour", WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON, 20+315, Y+115, 90, 30, hwnd, (HMENU)ID_RPT3 , NULL, NULL); show_prepare(); } // // Split the command line into space separated arguments. // #define MAX_ARGS 100 static void split_cmdline( char *cmdline, int *argc, char *argv[]) { argv[0] = "ebnaut-tx.exe"; *argc = 1; char *p = cmdline; while( *argc < MAX_ARGS && *p) { if( isspace( *p)) { p++; continue; } if( *p == '"') { p++; argv[(*argc)++] = p; while( *p && *p != '"') p++; } else { argv[(*argc)++] = p; while( *p && !isspace( *p)) p++; } if( *p) *p++ = 0; } } static void usage( void) { fprintf( stderr, "options:\n" "\n" " -p poly Polynomial set, or alias\n" " -k crclen CRC length in bits, 8 to 32 (default 16)\n" " -S secs Symbol period (default 1.0)\n" ); exit( 0); } int WINAPI WinMain( HINSTANCE hInstance, __attribute__ ((unused)) HINSTANCE hPrevInst, LPTSTR cmdline, int nShowCmd) { char *argv[MAX_ARGS]; int argc; split_cmdline( cmdline, &argc, argv); while( 1) { int c = getopt( argc, argv, "S:p:k:B"); if( c == -1) break; if( c == 'S') sym_period = atof( optarg); else if( c == 'p') poly_string = strdup( optarg); else if( c == 'k') CRCLEN = atoi( optarg); else if( c == '?') usage(); else bailout( "unrecognised option %c", c); } if( CRCLEN) if( CRCLEN < CRCBASE || CRCLEN > CRCMAX) bailout( "invalid CRC length"); WNDCLASS wc; memset( &wc, 0, sizeof( wc)); wc.lpszClassName = "Static Control"; wc.hInstance = hInstance; wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE); wc.lpfnWndProc = main_event; wc.hCursor = LoadCursor( 0, IDC_ARROW); RegisterClass(&wc); char title[100]; sprintf( title, "EbNaut Sender V%s", VERSION); HWND h = CreateWindow( wc.lpszClassName, title, WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, 455, 620, 0, 0, hInstance, 0); ShowWindow( h, nShowCmd); UpdateWindow( h); SetTimer( h, ID_TIMER, 20, NULL); MSG msg; while( GetMessage(&msg, NULL, 0, 0)) { TranslateMessage( &msg); DispatchMessage( &msg); } return (int) msg.wParam; }