diff --git a/WinRelease/hsmodem.exe b/WinRelease/hsmodem.exe index 30fc08c..a9b46e5 100755 Binary files a/WinRelease/hsmodem.exe and b/WinRelease/hsmodem.exe differ diff --git a/WinRelease/oscardata.exe b/WinRelease/oscardata.exe index 43795df..b43db5d 100755 Binary files a/WinRelease/oscardata.exe and b/WinRelease/oscardata.exe differ diff --git a/hsmodem/Makefile b/hsmodem/Makefile index 4b5640c..9e850a2 100755 --- a/hsmodem/Makefile +++ b/hsmodem/Makefile @@ -2,7 +2,7 @@ CXXFLAGS = -Wall -O3 -std=c++0x -Wno-write-strings -Wno-narrowing LDFLAGS = -lpthread -lrt -lsndfile -lasound -lm -lbass -lbassflac -lfftw3 -lfftw3_threads -lliquid -OBJ = hsmodem.o constellation.o crc16.o frame_packer.o main_helper.o scrambler.o speed.o fec.o audio.o udp.o fft.o liquid_if.o +OBJ = hsmodem.o constellation.o crc16.o frame_packer.o main_helper.o scrambler.o speed.o fec.o audio.o udp.o fft.o liquid_if.o symboltracker.o default: $(OBJ) g++ $(CXXFLAGS) -o ../LinuxRelease/hsmodem $(OBJ) $(LDFLAGS) diff --git a/hsmodem/audio.cpp b/hsmodem/audio.cpp index edfabd6..80e3930 100755 --- a/hsmodem/audio.cpp +++ b/hsmodem/audio.cpp @@ -369,7 +369,7 @@ void setPBvolume(int v) float vf = v; vf /= 100; - printf("set PB volume to:%d / %f [0..1]\n", v, vf ); + //printf("set PB volume to:%d / %f [0..1]\n", v, vf ); selectPBdevice(); if (!BASS_SetVolume(vf)) @@ -383,7 +383,7 @@ void setCAPvolume(int v) float vf = v; vf /= 100; - printf("set CAP volume to:%d / %f [0..1]\n", v, vf); + //printf("set CAP volume to:%d / %f [0..1]\n", v, vf); selectCAPdevice(); if (!BASS_RecordSetInput(-1,BASS_INPUT_ON,vf)) @@ -462,7 +462,7 @@ void PB_UNLOCK() { pthread_mutex_unlock(&pb_crit_sec); } #define AUDIO_BUFFERMAXTIME 2 // fifo can buffer this time in [s] #define AUDIO_PLAYBACK_BUFLEN (48000 * 10) // space for 10 seconds of samples -#define AUDIO_CAPTURE_BUFLEN (48000 * 10) +#define AUDIO_CAPTURE_BUFLEN (48000) // space for 1s int cap_wridx=0; int cap_rdidx=0; @@ -487,6 +487,11 @@ void init_pipes() // overwrite old data if the fifo is full void cap_write_fifo(float sample) { + if (((cap_wridx + 1) % AUDIO_CAPTURE_BUFLEN) == cap_rdidx) + { + printf("cap fifo full\n"); + } + CAP_LOCK; cap_buffer[cap_wridx] = sample; if(++cap_wridx >= AUDIO_CAPTURE_BUFLEN) cap_wridx = 0; diff --git a/hsmodem/audio/amsat.flac b/hsmodem/audio/amsat.flac new file mode 100644 index 0000000..6f5f0f0 Binary files /dev/null and b/hsmodem/audio/amsat.flac differ diff --git a/hsmodem/audio_wasapi.cpp b/hsmodem/audio_wasapi.cpp index 8595796..84545a6 100755 --- a/hsmodem/audio_wasapi.cpp +++ b/hsmodem/audio_wasapi.cpp @@ -157,7 +157,7 @@ void setPBvolume(int v) if (vf < minPBvol) vf = minPBvol; if (vf > maxPBvol) vf = maxPBvol; - printf("set PB volume to:%d / %f [%f..%f]\n", v, vf, minPBvol, maxPBvol); + //printf("set PB volume to:%d / %f [%f..%f]\n", v, vf, minPBvol, maxPBvol); selectPBdevice_wasapi(); if (!BASS_WASAPI_SetVolume(BASS_WASAPI_CURVE_DB, vf)) @@ -216,16 +216,55 @@ DWORD CALLBACK PBcallback_wasapi(void* buffer, DWORD length, void* user) free(fdata); return length; } +/* +#define MCHECK 10 +void nullChecker(float fv, float *pbuf, DWORD len) +{ + static float farr[MCHECK]; + static int idx = 0; + static int f = 1; + static int anz = 0; + if (f) + { + f = 0; + for (int i = 0; i < MCHECK; i++) + farr[i] = 1; + } + + farr[idx] = fv; + idx++; + if (idx == MCHECK) idx = 0; + + float nu = 0; + for (int i = 0; i < MCHECK; i++) + { + nu += farr[i]; + } + + if (nu == 0) + { + // how many 00s ar in the current buffer + int a = 0; + for (unsigned int i = 0; i < len-1; i++) + { + if (pbuf[i] == 0 && pbuf[i+1] == 0) a++; + } + printf("=============== null sequence detected: %d len:%d nullanz:%d\n",anz++,len,a); + } +} +*/ DWORD CALLBACK CAPcallback_wasapi(void* buffer, DWORD length, void* user) { //printf("CAP callback, len:%d\n",length); //measure_speed_bps(length/sizeof(float)/ WASAPI_CHANNELS); float* fbuffer = (float*)buffer; - //showbytestringf((char*)"rx: ", fbuffer, 20); + //showbytestringf((char*)"rx: ", fbuffer, 10); + //printf("%10.6f\n", fbuffer[0]); for (unsigned int i = 0; i < (length / sizeof(float)); i += WASAPI_CHANNELS) { + //nullChecker(fbuffer[i],fbuffer, length / sizeof(float)); cap_write_fifo(fbuffer[i]); } diff --git a/hsmodem/frame_packer.cpp b/hsmodem/frame_packer.cpp index 4e56ddf..0461514 100755 --- a/hsmodem/frame_packer.cpp +++ b/hsmodem/frame_packer.cpp @@ -32,6 +32,7 @@ uint8_t rx_status = 0; int framecounter = 0; int lastframenum = 0; +int getPayload_error = 0; // header for TX, uint8_t TXheaderbytes[HEADERLEN] = {0x53, 0xe1, 0xa6}; @@ -95,7 +96,7 @@ uint8_t *Pack(uint8_t *payload, int type, int status, int *plen) // polulate the raw frame // make the frame counter - if(status & (1<<4)) + if(status == 0) framecounter = 0; // first block of a stream else framecounter++; @@ -131,30 +132,46 @@ uint8_t *Pack(uint8_t *payload, int type, int status, int *plen) return txblock; } +#ifdef _WIN32_ +#define MAXHEADERRS 5 +#endif -#define MAXHEADERRS 0 +#ifdef _LINUX_ +#define MAXHEADERRS 2 // takes less CPU time, important for Rasberry PI +#endif /* * Header erros will not cause any data errors because the CRC will filter out * false header detects, * but it will cause higher CPU load due to excessive execution of FEC and CRC -*/ -int seekHeadersyms() + */ + +int seekHeadersyms(int symnum) { + int ret = -1; + int errs = 0; + + int exp_hdr = (UDPBLOCKLEN * 8) / bitsPerSymbol; // we expect a new header at this symbol number + symnum %= exp_hdr; + + int maxerr = MAXHEADERRS; + if(constellationSize == 4) { // QPSK for(int tab=0; tab<4; tab++) { - int errs = 0; + errs = 0; for(int i=0; i> 8; // measured line speed payload[6] = speed; - payload[7] = 0; // free for later use - payload[8] = 0; + payload[7] = maxLevel; // actual max level on sound capture in % + payload[8] = 0; // free for later use payload[9] = 0; //printf("Frame no.: %d, type:%d, minfo:%d\n",framenumrx,payload[0],payload[3]); diff --git a/hsmodem/hsmodem.cpp b/hsmodem/hsmodem.cpp index 0dc6560..8398a43 100755 --- a/hsmodem/hsmodem.cpp +++ b/hsmodem/hsmodem.cpp @@ -91,6 +91,7 @@ int UdpDataPort_fromGR_I_Q = 40137; int speedmode = 2; int bitsPerSymbol = 2; // QPSK=2, 8PSK=3 int constellationSize = 4; // QPSK=4, 8PSK=8 +int psk8mode=0; // 0=APSK8, 1=PSK8 char localIP[] = { "127.0.0.1" }; char ownfilename[] = { "hsmodem" }; @@ -202,7 +203,7 @@ int main(int argc, char* argv[]) //doArraySend(); if (demodulator() == 0) - sleep_ms(100); + sleep_ms(10); } printf("stopped: %d\n", keeprunning); @@ -242,6 +243,14 @@ SPEEDRATE sr[8] = { void startModem() { + if (speedmode >= 8) + { + speedmode = speedmode - 4; + psk8mode = 1; + } + else + psk8mode = 0; + bitsPerSymbol = sr[speedmode].bpsym; constellationSize = (1 << bitsPerSymbol); // QPSK=4, 8PSK=8 @@ -394,7 +403,7 @@ void appdata_rxdata(uint8_t* pdata, int len, struct sockaddr_in* rxsock) //if (getSending() == 1) return; // already sending (Array sending) - if (minfo == 0) + if (minfo == 0 || minfo == 3) { // this is the first frame of a larger file sendAnnouncement(); @@ -403,8 +412,8 @@ void appdata_rxdata(uint8_t* pdata, int len, struct sockaddr_in* rxsock) // caprate: samples/s. This are symbols: caprate/txinterpolfactor // and bits: symbols * bitsPerSymbol // and bytes/second: bits/8 = (caprate/txinterpolfactor) * bitsPerSymbol / 8 - // one frame has 258 bytes, so we need for 5s: 5* ((caprate/txinterpolfactor) * bitsPerSymbol / 8) /258 + 1 frames - int numframespreamble = 5 * ((caprate / txinterpolfactor) * bitsPerSymbol / 8) / 258 + 1; + // one frame has 258 bytes, so we need for 6s: 6* ((caprate/txinterpolfactor) * bitsPerSymbol / 8) /258 + 1 frames + int numframespreamble = 6 * ((caprate / txinterpolfactor) * bitsPerSymbol / 8) / 258 + 1; for (int i = 0; i < numframespreamble; i++) toGR_sendData(pdata + 2, type, minfo); } @@ -460,7 +469,7 @@ void GRdata_rxdata(uint8_t* pdata, int len, struct sockaddr_in* rxsock) if (++fnd >= (wt * ws)) { fnd = 0; - //printf("no signal detected %d, reset RX modem\n", wt); + printf("no signal detected %d, reset RX modem\n", wt); resetModem(); } } diff --git a/hsmodem/hsmodem.h b/hsmodem/hsmodem.h index 1f9803d..2e406f9 100755 --- a/hsmodem/hsmodem.h +++ b/hsmodem/hsmodem.h @@ -1,3 +1,4 @@ +#pragma once #ifdef _WIN32 #define _WIN32_ @@ -60,6 +61,7 @@ #include "frameformat.h" #include "fec.h" #include "udp.h" +#include "symboltracker.h" #define jpg_tempfilename "rxdata.jpg" @@ -129,6 +131,14 @@ void exit_fft(); void showbytestringf(char* title, float* data, int anz); uint16_t* make_waterfall(float fre, int* retlen); +void km_symtrack_cccf_create(int _ftype, + unsigned int _k, + unsigned int _m, + float _beta, + int _ms); +void km_symtrack_cccf_reset(int mode); +void km_symtrack_cccf_set_bandwidth(float _bw); +void km_symtrack_execute(liquid_float_complex _x, liquid_float_complex* _y, unsigned int* _ny, unsigned int* psym_out); extern int speedmode; extern int bitsPerSymbol; @@ -146,6 +156,8 @@ extern int announcement; extern int ann_running; extern int transmissions; extern int linespeed; +extern uint8_t maxLevel; +extern int psk8mode; #ifdef _LINUX_ int isRunning(char* prgname); diff --git a/hsmodem/hsmodem.vcxproj b/hsmodem/hsmodem.vcxproj index 0087344..149458e 100755 --- a/hsmodem/hsmodem.vcxproj +++ b/hsmodem/hsmodem.vcxproj @@ -228,6 +228,7 @@ + @@ -243,6 +244,7 @@ + diff --git a/hsmodem/hsmodem.vcxproj.filters b/hsmodem/hsmodem.vcxproj.filters index 5736df1..e85a854 100755 --- a/hsmodem/hsmodem.vcxproj.filters +++ b/hsmodem/hsmodem.vcxproj.filters @@ -54,6 +54,9 @@ Source Files + + Source Files + @@ -86,5 +89,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/hsmodem/liquid_if.cpp b/hsmodem/liquid_if.cpp index 7ca0fa7..e7b384f 100755 --- a/hsmodem/liquid_if.cpp +++ b/hsmodem/liquid_if.cpp @@ -51,9 +51,13 @@ modulation_scheme getMod() { if(bitsPerSymbol == 2) return LIQUID_MODEM_QPSK; - //return LIQUID_MODEM_APSK4; - else - return LIQUID_MODEM_APSK8; + else + { + if(psk8mode == 0) + return LIQUID_MODEM_APSK8; + else + return LIQUID_MODEM_PSK8; + } } // =========== MODULATOR ================================================== @@ -209,7 +213,6 @@ void modulator(uint8_t sym_in) nco_crcf dnnco = NULL; symtrack_cccf symtrack = NULL; -modem demod = NULL; firdecim_crcf decim = NULL; // decimator parameters @@ -221,8 +224,9 @@ int ftype_st = LIQUID_FIRFILT_RRC; unsigned int k_st = 4; // samples per symbol unsigned int m_st = 7; // filter delay (symbols) float beta_st = beta_excessBW;//0.30f; // filter excess bandwidth factor -float bandwidth_st = 0.7f; // loop filter bandwidth +float bandwidth_st = 0.9f; // loop filter bandwidth +uint8_t maxLevel = 0; // maximum level over the last x samples in % void init_demodulator() { @@ -241,34 +245,23 @@ void init_demodulator() // create symbol tracking synchronizer //k_st = txinterpolfactor; - symtrack = symtrack_cccf_create(ftype_st,k_st,m_st,beta_st,getMod()); - symtrack_cccf_set_bandwidth(symtrack,bandwidth_st); - - int ret = symtrack_cccf_set_eq_dd(symtrack); - if (ret != LIQUID_OK) - { - printf("symtrack_cccf_set_eq_dd failed\n"); - } - - // demodulator - demod = modem_create(getMod()); - printf("RX demodulator running\n"); + //symtrack = km_symtrack_cccf_create(ftype_st,k_st,m_st,beta_st,getMod()); + km_symtrack_cccf_create(ftype_st, k_st, m_st, beta_st, getMod()); + //symtrack_cccf_set_bandwidth(symtrack,bandwidth_st); + km_symtrack_cccf_set_bandwidth(bandwidth_st); } void close_demodulator() { - if(symtrack != NULL) symtrack_cccf_destroy(symtrack); - if(demod != NULL) modem_destroy(demod); if(decim != NULL) firdecim_crcf_destroy(decim); symtrack = NULL; - demod = NULL; decim = NULL; } void resetModem() { //printf("Reset Symtrack\n"); - symtrack_cccf_reset(symtrack); + km_symtrack_cccf_reset(0xff); } // called for Audio-Samples (FFT) @@ -301,6 +294,34 @@ void make_FFTdata(float f) } } +#define MCHECK 1000 +void getMax(float fv) +{ + static float farr[MCHECK]; + static int idx = 0; + static int f = 1; + + if (f) + { + f = 0; + for (int i = 0; i < MCHECK; i++) + farr[i] = 1; + } + + farr[idx] = fv; + idx++; + if (idx == MCHECK) + { + idx = 0; + float max = 0; + for (int i = 0; i < MCHECK; i++) + { + if (farr[i] > max) max = farr[i]; + } + maxLevel = (uint8_t)(max*100); + //printf("max: %10.6f\n", max); + } +} int demodulator() { @@ -313,13 +334,16 @@ static int ccol_idx = 0; float f; int ret = cap_read_fifo(&f); if(ret == 0) return 0; + // input volume #ifdef _WIN32_ f *= softwareCAPvolume; #endif - make_FFTdata(f*120); + getMax(f); + + make_FFTdata(f*60); // downconvert into baseband // still at soundcard sample rate @@ -340,19 +364,22 @@ static int ccol_idx = 0; ccol_idx = 0; // we have rxPreInterpolfactor samples in ccol + //printf("sc:%10.6f dn:%10.6f j%10.6f ", f, c.real, c.imag); liquid_float_complex y; firdecim_crcf_execute(decim, ccol, &y); unsigned int num_symbols_sync; liquid_float_complex syms; - symtrack_cccf_execute(symtrack, y, &syms, &num_symbols_sync); + //symtrack_cccf_execute(symtrack, y, &syms, &num_symbols_sync); + unsigned int nsym_out; // output symbol + km_symtrack_execute(y, &syms, &num_symbols_sync,&nsym_out); if(num_symbols_sync > 1) printf("symtrack_cccf_execute %d output symbols ???\n",num_symbols_sync); if(num_symbols_sync != 0) { unsigned int sym_out; // output symbol - modem_demodulate(demod, syms, &sym_out); - + sym_out = nsym_out; + measure_speed_syms(1); // try to extract a complete frame diff --git a/hsmodem/main_helper.cpp b/hsmodem/main_helper.cpp index 667be34..fd9bd81 100755 --- a/hsmodem/main_helper.cpp +++ b/hsmodem/main_helper.cpp @@ -89,7 +89,7 @@ void showbytestringf(char* title, float* data, int anz) { printf("%s. Len %d: ", title, anz); for (int i = 0; i < anz; i++) - printf("%.6f ", data[i]); + printf("%7.4f ", data[i]); printf("\n"); } diff --git a/hsmodem/symboltracker.cpp b/hsmodem/symboltracker.cpp new file mode 100755 index 0000000..93d6d1c --- /dev/null +++ b/hsmodem/symboltracker.cpp @@ -0,0 +1,173 @@ +#include "hsmodem.h" + +SYMTRACK km_symtrack; +SYMTRACK* q = &km_symtrack; + +// create km_symtrack object with basic parameters +// _ftype : filter type (e.g. LIQUID_FIRFILT_RRC) +// _k : samples per symbol +// _m : filter delay (symbols) +// _beta : filter excess bandwidth +// _ms : modulation scheme (e.g. LIQUID_MODEM_QPSK) +void km_symtrack_cccf_create(int _ftype, + unsigned int _k, + unsigned int _m, + float _beta, + int _ms) +{ + // validate input + if (_k < 2) + printf((char *)"symtrack_cccf_create(), filter samples/symbol must be at least 2\n"); + if (_m == 0) + printf((char*)"symtrack_cccf_create(), filter delay must be greater than zero\n"); + if (_beta <= 0.0f || _beta > 1.0f) + printf((char*)"symtrack_cccf_create(), filter excess bandwidth must be in [0,1]\n"); + if (_ms == LIQUID_MODEM_UNKNOWN || _ms >= LIQUID_MODEM_NUM_SCHEMES) + printf((char*)"symtrack_cccf_create(), invalid modulation scheme\n"); + + // set input parameters + q->filter_type = _ftype; + q->k = _k; + q->m = _m; + q->beta = _beta; + q->mod_scheme = _ms == LIQUID_MODEM_UNKNOWN ? LIQUID_MODEM_BPSK : _ms; + + // create automatic gain control + q->agc = agc_crcf_create(); + + // create symbol synchronizer (output rate: 2 samples per symbol) + if (q->filter_type == LIQUID_FIRFILT_UNKNOWN) + q->symsync = symsync_crcf_create_kaiser(q->k, q->m, 0.9f, 16); + else + q->symsync = symsync_crcf_create_rnyquist(q->filter_type, q->k, q->m, q->beta, 16); + symsync_crcf_set_output_rate(q->symsync, 2); + + // create equalizer as default low-pass filter with integer symbol delay (2 samples/symbol) + q->eq_len = 2 * 4 + 1; + q->eq = eqlms_cccf_create_lowpass(q->eq_len, 0.45f); + q->eq_strategy = q->SYMTRACK_EQ_DD; + + // nco and phase-locked loop + q->nco = nco_crcf_create(LIQUID_VCO); + + // demodulator + q->demod = modem_create((modulation_scheme)q->mod_scheme); + + // set default bandwidth + km_symtrack_cccf_set_bandwidth(0.9f); + + // reset and return main object + km_symtrack_cccf_reset(0xff); +} + +void km_symtrack_cccf_reset(int mode) +{ + // reset objects + if (mode & 1) agc_crcf_reset(q->agc); + if (mode & 2) symsync_crcf_reset(q->symsync); + if (mode & 4) eqlms_cccf_reset(q->eq); + if (mode & 8) nco_crcf_reset(q->nco); + if (mode & 0x10) modem_reset(q->demod); + + // reset internal counters + q->symsync_index = 0; + q->num_syms_rx = 0; +} + +void km_symtrack_cccf_set_bandwidth(float _bw) +{ + // validate input + if (_bw < 0) + printf("symtrack_set_bandwidth(), bandwidth must be in [0,1]\n"); + + // set bandwidths accordingly + float agc_bandwidth = 0.02f * _bw; + float symsync_bandwidth = 0.001f * _bw; + float eq_bandwidth = 0.02f * _bw; + float pll_bandwidth = 0.001f * _bw; + + // automatic gain control + agc_crcf_set_bandwidth(q->agc, agc_bandwidth); + + // symbol timing recovery + symsync_crcf_set_lf_bw(q->symsync, symsync_bandwidth); + + // equalizer + eqlms_cccf_set_bw(q->eq, eq_bandwidth); + + // phase-locked loop + nco_crcf_pll_set_bandwidth(q->nco, pll_bandwidth); +} + +#define MX 10 + +// execute synchronizer on single input sample +// _q : synchronizer object +// _x : input data sample +// _y : output data array +// _ny : number of samples written to output buffer +void km_symtrack_execute(liquid_float_complex _x, liquid_float_complex* _y, unsigned int* _ny, unsigned int *psym_out) +{ + liquid_float_complex v; // output sample + unsigned int i; + unsigned int num_outputs = 0; + + // run sample through automatic gain control + agc_crcf_execute(q->agc, _x, &v); + + // symbol synchronizer + unsigned int nw = 0; + symsync_crcf_execute(q->symsync, &v, 1, q->symsync_buf, &nw); + + // process each output sample + for (i = 0; i < nw; i++) { + // update phase-locked loop + nco_crcf_step(q->nco); + nco_crcf_mix_down(q->nco, q->symsync_buf[i], &v); + + // equalizer/decimator + eqlms_cccf_push(q->eq, v); + + // decimate result, noting that symsync outputs at exactly 2 samples/symbol + q->symsync_index++; + if (!(q->symsync_index % 2)) + continue; + + // increment number of symbols received + q->num_syms_rx++; + + // compute equalizer filter output; updating coefficients is dependent upon strategy + liquid_float_complex d_hat; + eqlms_cccf_execute(q->eq, &d_hat); + + // demodulate result, apply phase correction + unsigned int sym_out; + modem_demodulate(q->demod, d_hat, &sym_out); + *psym_out = sym_out; + float phase_error = modem_get_demodulator_phase_error(q->demod); + + // update pll + nco_crcf_pll_step(q->nco, phase_error); + + // update equalizer independent of the signal: estimate error + // assuming constant modulus signal + // TODO: check lock conditions of previous object to determine when to run equalizer + liquid_float_complex d_prime; + d_prime.real = d_prime.imag = 0; + if (q->num_syms_rx > 200) + { + modem_get_demodulator_sample(q->demod, &d_prime); + eqlms_cccf_step(q->eq, d_prime, d_hat); + } + + // save result to output + _y[num_outputs++] = d_hat; + } + + /*float fr = nco_crcf_get_frequency(q->nco); + float ph = nco_crcf_get_phase(q->nco); + + printf("%10.6f %10.6f %10.6f %10.6f\n", fr, ph, _x.real, _x.imag);*/ + + * _ny = num_outputs; +} diff --git a/hsmodem/symboltracker.h b/hsmodem/symboltracker.h new file mode 100755 index 0000000..a8c69d9 --- /dev/null +++ b/hsmodem/symboltracker.h @@ -0,0 +1,42 @@ +#pragma once + +#include "liquid.h" + +typedef struct _SYMTRACK_ { + // parameters + int filter_type; // filter type (e.g. LIQUID_FIRFILT_RRC) + unsigned int k; // samples/symbol + unsigned int m; // filter semi-length + float beta; // filter excess bandwidth + int mod_scheme; // demodulator + + // automatic gain control + agc_crcf agc; // agc object + float agc_bandwidth; // agc bandwidth + + // symbol timing recovery + symsync_crcf symsync; // symbol timing recovery object + float symsync_bandwidth; // symsync loop bandwidth + liquid_float_complex symsync_buf[8]; // symsync output buffer + unsigned int symsync_index; // symsync output sample index + + // equalizer/decimator + eqlms_cccf eq; // equalizer (LMS) + unsigned int eq_len; // equalizer length + float eq_bandwidth; // equalizer bandwidth + enum { + SYMTRACK_EQ_CM, // equalizer strategy: constant modulus + SYMTRACK_EQ_DD, // equalizer strategy: decision directed + SYMTRACK_EQ_OFF, // equalizer strategy: disabled + } eq_strategy; + + // nco/phase-locked loop + nco_crcf nco; // nco (carrier recovery) + float pll_bandwidth; // phase-locked loop bandwidth + + // demodulator + modem demod; // linear modem demodulator + + // state and counters + unsigned int num_syms_rx; // number of symbols recovered +} SYMTRACK; diff --git a/images/hsmodem_win10_141120.zip b/images/hsmodem_win10_141120.zip new file mode 100644 index 0000000..ead35c7 Binary files /dev/null and b/images/hsmodem_win10_141120.zip differ diff --git a/oscardata/oscardata/ArraySend.cs b/oscardata/oscardata/ArraySend.cs index db85063..142a06e 100755 --- a/oscardata/oscardata/ArraySend.cs +++ b/oscardata/oscardata/ArraySend.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.InteropServices; using System.Threading; // Input: Byte Array @@ -79,8 +78,10 @@ namespace oscardata long filesize = data.Length;// statics.GetFileSize(filename); Byte[] fnarr = statics.StringToByteArray(realname); + + // CRC16 over complete file contents is the file ID Crc c = new Crc(); - UInt16 fncrc = c.crc16_messagecalc(fnarr, fnarr.Length); + UInt16 fncrc = c.crc16_messagecalc(data, data.Length); // create the file header // 50 bytes ... Filename (or first 50 chars of the filename) @@ -151,13 +152,14 @@ namespace oscardata if (txlen <= statics.PayloadLen) { // we just need to send one frame - txudp(txdata, txtype, statics.LastFrame); + txudp(txdata, txtype, statics.SingleFrame); setSending(false); // transmission complete } else { // additional frame follow // from txdata send one chunk of length statics.PayloadLen + // frame is repeated for preamble by hsmodem.cpp Array.Copy(txdata, 0, txarr, 0, statics.PayloadLen); txudp(txarr, txtype, statics.FirstFrame); txpos = statics.PayloadLen; diff --git a/oscardata/oscardata/Form1.Designer.cs b/oscardata/oscardata/Form1.Designer.cs index 2e0c29a..e37db4b 100755 --- a/oscardata/oscardata/Form1.Designer.cs +++ b/oscardata/oscardata/Form1.Designer.cs @@ -77,6 +77,7 @@ this.tb_shutdown = new System.Windows.Forms.TextBox(); this.bt_resetmodem = new System.Windows.Forms.Button(); this.textBox2 = new System.Windows.Forms.TextBox(); + this.textBox3 = new System.Windows.Forms.TextBox(); this.groupBox3 = new System.Windows.Forms.GroupBox(); this.label6 = new System.Windows.Forms.Label(); this.label5 = new System.Windows.Forms.Label(); @@ -100,7 +101,10 @@ this.timer_searchmodem = new System.Windows.Forms.Timer(this.components); this.progressBar_fifo = new System.Windows.Forms.ProgressBar(); this.label_fifo = new System.Windows.Forms.Label(); - this.textBox3 = new System.Windows.Forms.TextBox(); + this.trackBar_maxlevel = new System.Windows.Forms.TrackBar(); + this.tb_info = new System.Windows.Forms.TextBox(); + this.label7 = new System.Windows.Forms.Label(); + this.cb_stampinfo = new System.Windows.Forms.CheckBox(); this.statusStrip1.SuspendLayout(); this.tabPage1.SuspendLayout(); this.tabPage2.SuspendLayout(); @@ -115,6 +119,7 @@ ((System.ComponentModel.ISupportInitialize)(this.tb_CAPvol)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.tb_PBvol)).BeginInit(); this.groupBox2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trackBar_maxlevel)).BeginInit(); this.SuspendLayout(); // // timer_udpTX @@ -608,6 +613,18 @@ this.textBox2.Text = "in case the RX has sync\r\nproblems, it can be\r\nre-initialized here."; this.textBox2.Visible = false; // + // textBox3 + // + this.textBox3.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.textBox3.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.textBox3.ForeColor = System.Drawing.Color.Black; + this.textBox3.Location = new System.Drawing.Point(15, 46); + this.textBox3.Multiline = true; + this.textBox3.Name = "textBox3"; + this.textBox3.Size = new System.Drawing.Size(151, 50); + this.textBox3.TabIndex = 12; + this.textBox3.Text = "only uncheck if modem runs on a separate PC"; + // // groupBox3 // this.groupBox3.Controls.Add(this.label6); @@ -709,6 +726,9 @@ // // groupBox2 // + this.groupBox2.Controls.Add(this.cb_stampinfo); + this.groupBox2.Controls.Add(this.tb_info); + this.groupBox2.Controls.Add(this.label7); this.groupBox2.Controls.Add(this.textBox5); this.groupBox2.Controls.Add(this.cb_announcement); this.groupBox2.Controls.Add(this.textBox4); @@ -786,7 +806,7 @@ this.tb_callsign.CharacterCasing = System.Windows.Forms.CharacterCasing.Upper; this.tb_callsign.Location = new System.Drawing.Point(71, 28); this.tb_callsign.Name = "tb_callsign"; - this.tb_callsign.Size = new System.Drawing.Size(151, 20); + this.tb_callsign.Size = new System.Drawing.Size(104, 20); this.tb_callsign.TabIndex = 1; // // label1 @@ -803,7 +823,7 @@ this.cb_stampcall.AutoSize = true; this.cb_stampcall.Checked = true; this.cb_stampcall.CheckState = System.Windows.Forms.CheckState.Checked; - this.cb_stampcall.Location = new System.Drawing.Point(71, 67); + this.cb_stampcall.Location = new System.Drawing.Point(71, 64); this.cb_stampcall.Name = "cb_stampcall"; this.cb_stampcall.Size = new System.Drawing.Size(146, 17); this.cb_stampcall.TabIndex = 2; @@ -815,7 +835,7 @@ this.cb_savegoodfiles.AutoSize = true; this.cb_savegoodfiles.Checked = true; this.cb_savegoodfiles.CheckState = System.Windows.Forms.CheckState.Checked; - this.cb_savegoodfiles.Location = new System.Drawing.Point(71, 90); + this.cb_savegoodfiles.Location = new System.Drawing.Point(71, 102); this.cb_savegoodfiles.Name = "cb_savegoodfiles"; this.cb_savegoodfiles.Size = new System.Drawing.Size(159, 17); this.cb_savegoodfiles.TabIndex = 3; @@ -833,10 +853,14 @@ "5500 8APSK BW: 2300 Hz", "6000 8APSK BW: 2500 Hz (QO-100)", "6600 8APSK BW: 2600 Hz", - "7200 8APSK BW: 2700 Hz"}); - this.cb_speed.Location = new System.Drawing.Point(636, 593); + "7200 8APSK BW: 2700 Hz", + "5500 8PSK BW: 2300 Hz", + "6000 8PSK BW: 2500 Hz (QO-100)", + "6600 8PSK BW: 2600 Hz", + "7200 8PSK BW: 2700 Hz"}); + this.cb_speed.Location = new System.Drawing.Point(658, 591); this.cb_speed.Name = "cb_speed"; - this.cb_speed.Size = new System.Drawing.Size(324, 21); + this.cb_speed.Size = new System.Drawing.Size(304, 21); this.cb_speed.TabIndex = 11; this.cb_speed.Text = "4410 QPSK BW: 2500 Hz (QO-100)"; this.cb_speed.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged); @@ -844,7 +868,7 @@ // label_speed // this.label_speed.AutoSize = true; - this.label_speed.Location = new System.Drawing.Point(545, 596); + this.label_speed.Location = new System.Drawing.Point(567, 594); this.label_speed.Name = "label_speed"; this.label_speed.Size = new System.Drawing.Size(71, 13); this.label_speed.TabIndex = 12; @@ -857,10 +881,10 @@ // // progressBar_fifo // - this.progressBar_fifo.Location = new System.Drawing.Point(636, 620); + this.progressBar_fifo.Location = new System.Drawing.Point(658, 618); this.progressBar_fifo.Maximum = 20; this.progressBar_fifo.Name = "progressBar_fifo"; - this.progressBar_fifo.Size = new System.Drawing.Size(324, 23); + this.progressBar_fifo.Size = new System.Drawing.Size(304, 23); this.progressBar_fifo.Step = 11; this.progressBar_fifo.Style = System.Windows.Forms.ProgressBarStyle.Continuous; this.progressBar_fifo.TabIndex = 13; @@ -868,29 +892,59 @@ // label_fifo // this.label_fifo.AutoSize = true; - this.label_fifo.Location = new System.Drawing.Point(545, 625); + this.label_fifo.Location = new System.Drawing.Point(567, 623); this.label_fifo.Name = "label_fifo"; this.label_fifo.Size = new System.Drawing.Size(55, 13); this.label_fifo.TabIndex = 14; this.label_fifo.Text = "TX Buffer:"; // - // textBox3 + // trackBar_maxlevel // - this.textBox3.BorderStyle = System.Windows.Forms.BorderStyle.None; - this.textBox3.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.textBox3.ForeColor = System.Drawing.Color.Black; - this.textBox3.Location = new System.Drawing.Point(15, 46); - this.textBox3.Multiline = true; - this.textBox3.Name = "textBox3"; - this.textBox3.Size = new System.Drawing.Size(151, 50); - this.textBox3.TabIndex = 12; - this.textBox3.Text = "only uncheck if modem runs on a separate PC"; + this.trackBar_maxlevel.Location = new System.Drawing.Point(535, 591); + this.trackBar_maxlevel.Maximum = 100; + this.trackBar_maxlevel.Name = "trackBar_maxlevel"; + this.trackBar_maxlevel.Orientation = System.Windows.Forms.Orientation.Vertical; + this.trackBar_maxlevel.Size = new System.Drawing.Size(45, 75); + this.trackBar_maxlevel.TabIndex = 15; + this.trackBar_maxlevel.TickFrequency = 10; + this.trackBar_maxlevel.TickStyle = System.Windows.Forms.TickStyle.TopLeft; + this.trackBar_maxlevel.Value = 50; + // + // tb_info + // + this.tb_info.Location = new System.Drawing.Point(243, 28); + this.tb_info.Name = "tb_info"; + this.tb_info.Size = new System.Drawing.Size(413, 20); + this.tb_info.TabIndex = 22; + this.tb_info.Text = "tnx fer QSO, vy 73"; + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(204, 31); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(28, 13); + this.label7.TabIndex = 21; + this.label7.Text = "Info:"; + // + // cb_stampinfo + // + this.cb_stampinfo.AutoSize = true; + this.cb_stampinfo.Checked = true; + this.cb_stampinfo.CheckState = System.Windows.Forms.CheckState.Checked; + this.cb_stampinfo.Location = new System.Drawing.Point(71, 82); + this.cb_stampinfo.Name = "cb_stampinfo"; + this.cb_stampinfo.Size = new System.Drawing.Size(128, 17); + this.cb_stampinfo.TabIndex = 23; + this.cb_stampinfo.Text = "Insert Info into picture"; + this.cb_stampinfo.UseVisualStyleBackColor = true; // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(1293, 691); + this.Controls.Add(this.trackBar_maxlevel); this.Controls.Add(this.label_fifo); this.Controls.Add(this.progressBar_fifo); this.Controls.Add(this.cb_speed); @@ -925,6 +979,7 @@ ((System.ComponentModel.ISupportInitialize)(this.tb_PBvol)).EndInit(); this.groupBox2.ResumeLayout(false); this.groupBox2.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.trackBar_maxlevel)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); @@ -1003,6 +1058,10 @@ private System.Windows.Forms.TextBox textBox4; private System.Windows.Forms.TextBox textBox1; private System.Windows.Forms.TextBox textBox3; + private System.Windows.Forms.TrackBar trackBar_maxlevel; + private System.Windows.Forms.CheckBox cb_stampinfo; + private System.Windows.Forms.TextBox tb_info; + private System.Windows.Forms.Label label7; } } diff --git a/oscardata/oscardata/Form1.cs b/oscardata/oscardata/Form1.cs index 9e9ee3e..46a7ae4 100755 --- a/oscardata/oscardata/Form1.cs +++ b/oscardata/oscardata/Form1.cs @@ -39,9 +39,9 @@ namespace oscardata Byte frameinfo = (Byte)statics.FirstFrame; String TXfilename; int rxbytecounter = 0; - DateTime starttime; String old_tsip = ""; bool modemrunning = false; + receivefile recfile = new receivefile(); public Form1() { @@ -240,9 +240,7 @@ namespace oscardata // RX timer int rxstat = 0; int speed; - int tmpnum = 0; - int file_lostframes = 0; - int last_fileid = 0; + int maxlevel = 0; private void timer_udprx_Tick(object sender, EventArgs e) { while (true) @@ -260,383 +258,92 @@ namespace oscardata speed = rxd[5]; speed <<= 8; speed += rxd[6]; - int dummy3 = rxd[7]; + maxlevel = rxd[7]; int dummy4 = rxd[8]; int dummy5 = rxd[9]; - if (rxstat == 4) - { - framelost++; - file_lostframes++; - } - calcBer(rxfrmnum); + rxbytecounter += statics.UdpBlocklen; - if (minfo == statics.FirstFrame) - file_lostframes = 0; + trackBar_maxlevel.Value = maxlevel; + int v1 = 255; + int v2 = 220; + if (maxlevel < 20 || maxlevel > 70) trackBar_maxlevel.BackColor = Color.FromArgb(v1,v2,v2); + else if (maxlevel < 30 || maxlevel > 60) trackBar_maxlevel.BackColor = Color.FromArgb(v1, v1, v2); + else trackBar_maxlevel.BackColor = Color.FromArgb(v2, v1, v2); - Byte[] rxdata = new byte[rxd.Length - 10]; + Byte[] rxdata = new byte[rxd.Length - 10]; Array.Copy(rxd, 10, rxdata, 0, rxd.Length - 10); //Console.WriteLine("minfo:" + minfo + " data:" + rxdata[0].ToString("X2") + " " + rxdata[1].ToString("X2")); - if (minfo == statics.FirstFrame) - { - rxbytecounter = rxdata.Length; - starttime = DateTime.UtcNow; - } - else - { - rxbytecounter += rxdata.Length; - } - TimeSpan ts = DateTime.UtcNow - starttime; - ts += new TimeSpan(0, 0, 0, 1); - - // ===== ASCII RX ================================================ - if (rxtype == statics.AsciiFile) - { - // if this is the first frame of a file transfer - // then read and remove the file info header - if (minfo == statics.FirstFrame || minfo == statics.SingleFrame) - { - //Console.WriteLine("first, single"); - rxdata = ArraySend.GetAndRemoveHeader(rxdata); - if (rxdata == null) return; - if (last_fileid == ArraySend.FileID) return; // got first frame for this ID already - last_fileid = ArraySend.FileID; - } - else - last_fileid = 0; - - // collect all received data into zip_RXtempfilename - Byte[] ba = null; - Byte[] nba; - try - { - ba = File.ReadAllBytes(statics.zip_RXtempfilename); - } - catch { } - - if (ba != null) - { - //Console.WriteLine("write next"); - nba = new Byte[ba.Length + rxdata.Length]; - Array.Copy(ba, nba, ba.Length); - Array.Copy(rxdata, 0, nba, ba.Length, rxdata.Length); - } - else - { - //Console.WriteLine("write first"); - nba = new Byte[rxdata.Length]; - Array.Copy(rxdata, nba, rxdata.Length); - } - File.WriteAllBytes(statics.zip_RXtempfilename, nba); - long filesize = 0; - - // check if transmission is finished - if (minfo == statics.LastFrame || minfo == statics.SingleFrame) - { - // statics.zip_RXtempfilename has the received data, but maybee too long (multiple of payload length) - // reduce for the real file length - Byte[] fc = File.ReadAllBytes(statics.zip_RXtempfilename); - Byte[] fdst = new byte[ArraySend.FileSize]; - if(fc.Length < ArraySend.FileSize) - { - Console.WriteLine("len=" + fc.Length + " fz=" + ArraySend.FileSize); - return; - } - Array.Copy(fc, 0, fdst, 0, ArraySend.FileSize); - File.WriteAllBytes(statics.zip_RXtempfilename, fdst); - - //Console.WriteLine("size:"+ ArraySend.FileSize.ToString()); - - //Console.WriteLine("last"); - // unzip received data and store result in file: unzipped_RXtempfilename - rtb_RXfile.Text = ""; - ZipStorer zs = new ZipStorer(); - String fl = zs.unzipFile(statics.zip_RXtempfilename); - if (fl != null) - { - // save file - int idx = fl.LastIndexOf('/'); - if (idx == -1) idx = fl.LastIndexOf('\\'); - String fdest = fl.Substring(idx + 1); - fdest = statics.getHomePath("", fdest); - try { File.Delete(fdest); } catch { } - File.Move(fl, fdest); - filesize = statics.GetFileSize(fdest); - - String serg = File.ReadAllText(fdest); - printText(rtb_RXfile, serg); - } - else - printText(rtb_RXfile, "unzip failed"); - File.Delete(statics.zip_RXtempfilename); - } - - int rest = ArraySend.FileSize - rxbytecounter; - if (rest < 0) rest = 0; - if (rest > 0) - label_rxfile.Text = "RX file: " + ArraySend.rxFilename + " " + rest.ToString() + " bytes"; - else - label_rxfile.Text = "RX file: " + ArraySend.rxFilename + " " + filesize + " bytes"; - - if (minfo == statics.LastFrame) - ShowStatus((int)filesize, (int)ts.TotalSeconds); - else - ShowStatus(rxbytecounter, (int)ts.TotalSeconds); - } - - // ===== HTML File RX ================================================ - if (rxtype == statics.HTMLFile) - { - // if this is the first frame of a file transfer - // then read and remove the file info header - if (minfo == statics.FirstFrame) - { - rxdata = ArraySend.GetAndRemoveHeader(rxdata); - if (last_fileid == ArraySend.FileID) return; // got first frame for this ID already - last_fileid = ArraySend.FileID; - } - else - last_fileid = 0; - - Byte[] ba = null; - Byte[] nba; - try - { - ba = File.ReadAllBytes(statics.zip_RXtempfilename); - } - catch { } - - if (ba != null) - { - nba = new Byte[ba.Length + rxdata.Length]; - Array.Copy(ba, nba, ba.Length); - Array.Copy(rxdata, 0, nba, ba.Length, rxdata.Length); - } - else - { - nba = new Byte[rxdata.Length]; - Array.Copy(rxdata, nba, rxdata.Length); - } - File.WriteAllBytes(statics.zip_RXtempfilename, nba); - long filesize = 0; - if (minfo == statics.LastFrame) - { - // unzip received data - rtb_RXfile.Text = ""; - ZipStorer zs = new ZipStorer(); - // unzip returns filename+path of unzipped file - String fl = zs.unzipFile(statics.zip_RXtempfilename); - if (fl != null) - { - // save file - int idx = fl.LastIndexOf('/'); - if (idx == -1) idx = fl.LastIndexOf('\\'); - String fdest = fl.Substring(idx + 1); - fdest = statics.getHomePath("", fdest); - try { File.Delete(fdest); } catch { } - File.Move(fl, fdest); - filesize = statics.GetFileSize(fdest); - - rxbytecounter = (int)statics.GetFileSize(fdest); - String serg = File.ReadAllText(fdest); - printText(rtb_RXfile, serg); - try - { - OpenUrl(fdest); - } - catch (Exception ex) - { - Console.WriteLine(ex.ToString()); - } - } - else - printText(rtb_RXfile, "unzip failed"); - } - - int rest = ArraySend.FileSize - rxbytecounter; - if (rest < 0) rest = 0; - if (rest > 0) - label_rxfile.Text = "RX file: " + ArraySend.rxFilename + " " + rest.ToString() + " bytes"; - else - label_rxfile.Text = "RX file: " + ArraySend.rxFilename + " " + filesize + " bytes"; - - if (minfo == statics.LastFrame) - ShowStatus(ArraySend.FileSize, (int)ts.TotalSeconds); - else - ShowStatus(rxbytecounter, (int)ts.TotalSeconds); - } - - // ===== Binary File RX ================================================ - if (rxtype == statics.BinaryFile) - { - // if this is the first frame of a file transfer - // then read and remove the file info header - if (minfo == statics.FirstFrame || minfo == statics.SingleFrame) - { - //Console.WriteLine("first, single"); - rxdata = ArraySend.GetAndRemoveHeader(rxdata); - if (last_fileid == ArraySend.FileID) return; // got first frame for this ID already - last_fileid = ArraySend.FileID; - } - else - last_fileid = 0; - - // collect all received data into zip_RXtempfilename - Byte[] ba = null; - Byte[] nba; - try - { - ba = File.ReadAllBytes(statics.zip_RXtempfilename); - } - catch { } - - if (ba != null) - { - //Console.WriteLine("write next"); - nba = new Byte[ba.Length + rxdata.Length]; - Array.Copy(ba, nba, ba.Length); - Array.Copy(rxdata, 0, nba, ba.Length, rxdata.Length); - } - else - { - //Console.WriteLine("write first"); - nba = new Byte[rxdata.Length]; - Array.Copy(rxdata, nba, rxdata.Length); - } - File.WriteAllBytes(statics.zip_RXtempfilename, nba); - long filesize = 0; - - // check if transmission is finished - if (minfo == statics.LastFrame || minfo == statics.SingleFrame) - { - // statics.zip_RXtempfilename has the received data, but maybee too long (multiple of payload length) - // reduce for the real file length - Byte[] fc = File.ReadAllBytes(statics.zip_RXtempfilename); - Byte[] fdst = new byte[ArraySend.FileSize]; - if (fc.Length < ArraySend.FileSize) - { - Console.WriteLine("len=" + fc.Length + " fz=" + ArraySend.FileSize); - return; - } - Console.WriteLine("copy final binary file"); - Array.Copy(fc, 0, fdst, 0, ArraySend.FileSize); - File.WriteAllBytes(statics.zip_RXtempfilename, fdst); - - //Console.WriteLine("last"); - // unzip received data and store result in file: unzipped_RXtempfilename - rtb_RXfile.Text = ""; - ZipStorer zs = new ZipStorer(); - String fl = zs.unzipFile(statics.zip_RXtempfilename); - if (fl != null) - { - int idx = fl.LastIndexOf('/'); - if(idx == -1) idx = fl.LastIndexOf('\\'); - String fdest = fl.Substring(idx + 1); - fdest = statics.getHomePath("", fdest); - try { File.Delete(fdest); } catch { } - File.Move(fl, fdest); - filesize = statics.GetFileSize(fdest); - //File.WriteAllBytes(fl, nba); - printText(rtb_RXfile, "binary file received\r\n"); - printText(rtb_RXfile, "--------------------\r\n\r\n"); - printText(rtb_RXfile, "file size : " + filesize + " byte\r\n\r\n"); - printText(rtb_RXfile, "stored in : " + fdest + "\r\n\r\n"); - printText(rtb_RXfile, "transmission time : " + ((int)ts.TotalSeconds).ToString() + " seconds" + "\r\n\r\n"); - printText(rtb_RXfile, "transmission speed: " + ((int)(filesize*8/ts.TotalSeconds)).ToString() + " bit/s" + "\r\n\r\n"); - } - else - printText(rtb_RXfile, "unzip failed"); - File.Delete(statics.zip_RXtempfilename); - } - - int rest = ArraySend.FileSize - rxbytecounter; - if (rest < 0) rest = 0; - if (rest > 0) - label_rxfile.Text = "RX file: " + ArraySend.rxFilename + " " + rest.ToString() + " bytes"; - else - label_rxfile.Text = "RX file: " + ArraySend.rxFilename + " " + filesize + " bytes"; - - if (minfo == statics.LastFrame) - ShowStatus((int)filesize, (int)ts.TotalSeconds); - else - ShowStatus(rxbytecounter, (int)ts.TotalSeconds); - } - - // ===== IMAGE RX ================================================ + // ========= receive file ========== + // handle file receive if (rxtype == statics.Image) { - // if this is the first frame of a file transfer - // then read and remove the file info header - if (minfo == statics.FirstFrame) + if (recfile.receive(rxd)) { - rxdata = ArraySend.GetAndRemoveHeader(rxdata); - if (rxdata == null) return; - } - - ih.receive_image(rxdata, minfo); - - // show currect contents of rxtemp.jpg in RX picturebox - try - { - String fn = statics.addTmpPath("temp" + tmpnum.ToString() + ".jpg"); - try + if (recfile.filename != null && recfile.filename.Length > 0 && minfo != statics.FirstFrame) { - File.Delete(fn); + // reception complete, show stored file + Console.WriteLine("load " + recfile.filename); + pictureBox_rximage.BackgroundImage = Image.FromFile(recfile.filename); + pictureBox_rximage.Invalidate(); } - catch { } - tmpnum++; - fn = statics.addTmpPath("temp" + tmpnum.ToString() + ".jpg"); - File.Copy(statics.jpg_tempfilename, fn); - - try + if (recfile.pbmp != null) { - if(statics.GetFileSize(fn) > 1200) - pictureBox_rximage.BackgroundImage = Image.FromFile(fn); - } - catch { - } - - if (minfo == statics.LastFrame) - { - // file is complete, save in RX storage - // remove possible path from filename - String fname = ArraySend.rxFilename; - int idx = fname.IndexOfAny(new char[] {'\\','/' }); - if (idx != -1) + // in case we can display portions of an image return this portion + try { - try - { - fname = fname.Substring(idx + 1); - } catch{ } - } - if (!cb_savegoodfiles.Checked || (file_lostframes == 0 && cb_savegoodfiles.Checked)) - { - // add home path and RXstorage path - String fnx = statics.getHomePath(statics.RXimageStorage, fname); - File.Copy(fn, fnx); + pictureBox_rximage.BackgroundImage = recfile.pbmp; } + catch { } } } - catch { } + } - int rest = ArraySend.FileSize - rxbytecounter; - if (rest < 0) rest = 0; - if(rest > 0) - label_rximage.Text = "RX image: " + ArraySend.rxFilename + " remaining: " + rest.ToString() + " bytes"; - else - label_rximage.Text = "RX image: " + ArraySend.rxFilename; - ShowStatus(rxbytecounter, (int)ts.TotalSeconds); + if (rxtype == statics.AsciiFile) + { + if(recfile.receive(rxd)) + { + // ASCII file received, show in window + String serg = File.ReadAllText(recfile.filename); + printText(rtb_RXfile, serg); + } + } + + if (rxtype == statics.HTMLFile) + { + if (recfile.receive(rxd)) + { + // HTML file received, show in window + String serg = File.ReadAllText(recfile.filename); + printText(rtb_RXfile, serg); + // and show in browser + OpenUrl(recfile.filename); + } + } + + if (rxtype == statics.BinaryFile) + { + if (recfile.receive(rxd)) + { + // Binary file received, show statistics in window + printText(rtb_RXfile, "binary file received\r\n"); + printText(rtb_RXfile, "--------------------\r\n\r\n"); + printText(rtb_RXfile, "transmission time : " + ((int)recfile.runtime.TotalSeconds).ToString() + " seconds" + "\r\n\r\n"); + printText(rtb_RXfile, "transmission speed: " + ((int)(recfile.filesize * 8 / recfile.runtime.TotalSeconds)).ToString() + " bit/s" + "\r\n\r\n"); + printText(rtb_RXfile, "file size : " + recfile.filesize + " byte\r\n\r\n"); + printText(rtb_RXfile, "file name : " + recfile.filename + "\r\n\r\n"); + } } // ===== BER Test ================================================ if (rxtype == statics.BERtest) { - RXstatus.Text = "BER: " + ber.ToString("E3") + " " + rxframecounter.ToString() + " frames received OK"; - - BERcheck(rxdata); + BERcheck(rxdata, rxfrmnum,minfo); } + + ShowStatus(rxtype, minfo); } } @@ -894,18 +601,21 @@ namespace oscardata Image img = new Bitmap(fullfn); String cs = tb_callsign.Text; if (cb_stampcall.Checked == false) cs = ""; + String inf = tb_info.Text; + if (cb_stampinfo.Checked == false) inf = ""; if (!checkBox_big.Checked) { - img = ih.ResizeImage(img, 320, 240, cs); + img = ih.ResizeImage(img, 320, 240, cs, inf); // set quality by reducing the file size and save under default name ih.SaveJpgAtFileSize(img, TXimagefilename, max_size / 2); } else { - img = ih.ResizeImage(img, 640, 480, cs); + img = ih.ResizeImage(img, 640, 480, cs, inf); // set quality by reducing the file size and save under default name ih.SaveJpgAtFileSize(img, TXimagefilename, max_size); } + pictureBox_tximage.Load(TXimagefilename); TXRealFileSize = statics.GetFileSize(TXimagefilename); ShowTXstatus(); @@ -977,11 +687,8 @@ namespace oscardata private void button_startBERtest_Click(object sender, EventArgs e) { - ber = 0; - framelost = 0; - totallostframes = 0; - last_rxfrmnum = -1; rtb.Text = ""; + missBlocks = 0; frameinfo = (Byte)statics.FirstFrame; txcommand = statics.BERtest; } @@ -991,36 +698,23 @@ namespace oscardata txcommand = statics.noTX; } - DateTime dt = DateTime.Now; int rxframecounter = 0; - int framelost = 0; - int last_rxfrmnum = -1; - double ber = 0; - int totallostframes = 0; - - void calcBer(int rxfrmnum) + int lastfrmnum = 0; + int missBlocks = 0; + private void BERcheck(Byte[] rxdata, int frmnum, int minfo) { - if (last_rxfrmnum == -1) - { - last_rxfrmnum = rxfrmnum; - return; - } + if (minfo == statics.FirstFrame) + rxframecounter = 0; - // calc gap - int gap = ((rxfrmnum+1024) - last_rxfrmnum) % 1024; - rxframecounter += gap; - totallostframes += (gap - 1); + if (lastfrmnum == frmnum) return; + lastfrmnum = frmnum; - int totalbits = rxframecounter * 258 * 8; - int errorbits = totallostframes * 258 * 8; - ber = (double)totallostframes / (double)rxframecounter; + rxframecounter++; - last_rxfrmnum = rxfrmnum; - } - - private void BERcheck(Byte[] rxdata) - { - String line = "RX: " + rxframecounter.ToString().PadLeft(6, ' ') + " "; + missBlocks += (frmnum - rxframecounter); + if (missBlocks < 0) missBlocks = 0; + String line = "RX: " + frmnum.ToString().PadLeft(6, ' ') + " "; // + rxframecounter + " " + missBlocks + " "; + rxframecounter = frmnum; // print payload (must be printable chars) line += Encoding.UTF8.GetString(rxdata).Substring(0, 50) + " ..."; @@ -1060,28 +754,72 @@ namespace oscardata line += " " + bits.ToString() + " " + sbit + " " + bytes.ToString() + " " + sbyt; - line += " BER: " + string.Format("{0:#.##E+0}", ber); // ber.ToString("E3"); - line += "\r\n"; printText(rtb,line); - - int fl = framelost; - if (fl <= 1) fl = 0; - String s = "Speed: " + speed.ToString() + " bit/s, Lost Frames: " + fl.ToString(); - toolStripStatusLabel.Text = s; } - private void ShowStatus(int rxbytecounter, int totalseconds) + int[] blockres = new int[2]; + private void ShowStatus(int rxtype, int minfo) { - int fl = framelost; - if (fl <= 1) fl = 0; - String s = "Speed: " + speed.ToString() + " bit/s, Lost Frames: " + fl.ToString(); - toolStripStatusLabel.Text = s; + if (minfo == statics.FirstFrame) + rxbytecounter = 0; - int rspeed = 0; - if (totalseconds >= 1) - rspeed = rxbytecounter * 8 / totalseconds; - RXstatus.Text = "received " + rxbytecounter + " byte " + totalseconds + " s, " + rspeed + " bit/s"; + // calculate speed + int fsz = (int)recfile.filesize; + if (fsz == 0) fsz = ArraySend.FileSize; // during reception we do not have the final size, use the transmitted size + // fsz = real or zipped file size, whatever available + // transmitted size in % of zipped file + int txsize = 0; + if (ArraySend.FileSize > 0) + txsize = (recfile.rxbytes * 100) / ArraySend.FileSize; + // transmitted size of real filesize + int txreal = (fsz * txsize) / 100; + // speed + int speed_bps = 0; + if(recfile.runtime.TotalSeconds > 0) + speed_bps = (int)(((double)txreal * 8.0) / recfile.runtime.TotalSeconds); + + // show RX status on top of the RX windows + String s = "RX: "; + + recfile.blockstat(blockres); + int missingBlocks = blockres[0] - blockres[1]; + + if (ArraySend.rxFilename != null && ArraySend.rxFilename.Length > 0) + { + s += ArraySend.rxFilename + " "; + s += recfile.rxbytes / 1000 + " of " + ArraySend.FileSize / 1000 + " kB "; + s += Math.Truncate(recfile.runtime.TotalSeconds) + " s, "; + s += blockres[1] + " of " + blockres[0] + " blocks OK"; + } + else + s += "wait for RX"; + + if (rxtype == statics.Image) + label_rximage.Text = s; + + if (rxtype == statics.AsciiFile || rxtype == statics.HTMLFile || rxtype == statics.BinaryFile) + label_rxfile.Text = s; + + // show speed in status line at the left side + toolStripStatusLabel.Text = "Line Speed: " + speed.ToString() + " bps"; + + if (missBlocks < 0) missBlocks = 0; + if (missingBlocks < 0) missingBlocks = 0; + + // show RX status in the status line + if (rxtype == statics.BERtest) + RXstatus.Text = "RXed: " + rxbytecounter + " Byte. Missing blocks: " + missBlocks; + else + { + if(fsz > 0) + RXstatus.Text = "RXed: " + fsz + " Byte. Missing blocks: " + missingBlocks; + else + RXstatus.Text = "RXed: " + rxbytecounter + " Byte. Missing blocks: " + missingBlocks; + } + + if(speed_bps > 0) + RXstatus.Text += " Net Speed:" + speed_bps + " bps"; } private void button_cancelimg_Click(object sender, EventArgs e) @@ -1139,7 +877,10 @@ namespace oscardata label_txfile.Location = new Point(rtb_TXfile.Location.X, ly); label_rxfile.Location = new Point(rtb_RXfile.Location.X, ly); - label_speed.Location = new Point(panel_txspectrum.Location.X + panel_txspectrum.Size.Width + 20,panel_txspectrum.Location.Y+10); + trackBar_maxlevel.Location = new Point(panel_txspectrum.Location.X + panel_txspectrum.Size.Width + 5, panel_txspectrum.Location.Y); + trackBar_maxlevel.Size = new Size(20, panel_txspectrum.Size.Height); + + label_speed.Location = new Point(trackBar_maxlevel.Location.X + trackBar_maxlevel.Size.Width + 15,panel_txspectrum.Location.Y+10); cb_speed.Location = new Point(label_speed.Location.X + label_speed.Size.Width + 10, label_speed.Location.Y-5); label_fifo.Location = new Point(label_speed.Location.X, label_speed.Location.Y + 35); @@ -1207,26 +948,6 @@ namespace oscardata statics.ModemIP = "1.2.3.4"; } - private void bt_file_ascii_Click(object sender, EventArgs e) - { - OpenFileDialog open = new OpenFileDialog(); - open.Filter = "Text Files(*.txt*; *.*)|*.txt; *.*"; - if (open.ShowDialog() == DialogResult.OK) - { - TXfilename = open.FileName; - TXRealFilename = open.SafeFileName; - String text = File.ReadAllText(TXfilename); - rtb_TXfile.Text = text; - txcommand = statics.AsciiFile; - // compress file - ZipStorer zs = new ZipStorer(); - zs.zipFile(statics.zip_TXtempfilename,open.SafeFileName,open.FileName); - - TXRealFileSize = statics.GetFileSize(statics.zip_TXtempfilename); - ShowTXstatus(); - } - } - private void bt_file_send_Click(object sender, EventArgs e) { rtb_RXfile.Text = ""; @@ -1236,36 +957,35 @@ namespace oscardata ArraySend.Send(textarr, (Byte)txcommand, TXfilename, TXRealFilename); } + private void bt_file_ascii_Click(object sender, EventArgs e) + { + bt_sendFile("Text Files(*.txt*; *.*)|*.txt; *.*", statics.AsciiFile); + } + private void button2_Click(object sender, EventArgs e) { - OpenFileDialog open = new OpenFileDialog(); - open.Filter = "HTML Files(*.html; *.htm; *.*)|*.html; *.htm; *.*"; - if (open.ShowDialog() == DialogResult.OK) - { - TXfilename = open.FileName; - TXRealFilename = open.SafeFileName; - String text = File.ReadAllText(TXfilename); - rtb_TXfile.Text = text; - txcommand = statics.HTMLFile; - // compress file - ZipStorer zs = new ZipStorer(); - zs.zipFile(statics.zip_TXtempfilename, open.SafeFileName, open.FileName); - - TXRealFileSize = statics.GetFileSize(statics.zip_TXtempfilename); - ShowTXstatus(); - } + bt_sendFile("HTML Files(*.html; *.htm; *.*)|*.html; *.htm; *.*", statics.HTMLFile); } private void bt_sendBinaryFile_Click(object sender, EventArgs e) + { + bt_sendFile("All Files(*.*)|*.*", statics.BinaryFile); + } + + private void bt_sendFile(String filter, int cmd) { OpenFileDialog open = new OpenFileDialog(); - open.Filter = "All Files(*.*)|*.*"; + open.Filter = filter; if (open.ShowDialog() == DialogResult.OK) { + txcommand = cmd; TXfilename = open.FileName; TXRealFilename = open.SafeFileName; - rtb_TXfile.Text = "Binary file " + TXfilename + " loaded"; - txcommand = statics.BinaryFile; + if (txcommand == statics.BinaryFile) + rtb_TXfile.Text = "Binary file " + TXfilename + " loaded"; + else + rtb_TXfile.Text = File.ReadAllText(TXfilename); + // compress file ZipStorer zs = new ZipStorer(); zs.zipFile(statics.zip_TXtempfilename, open.SafeFileName, open.FileName); @@ -1277,26 +997,17 @@ namespace oscardata private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { - int idx = cb_speed.SelectedIndex; - int real_rate=4000; - - switch (idx) - { - case 0: real_rate = 3000; break; - case 1: real_rate = 3150; break; - case 2: real_rate = 3675; break; - case 3: real_rate = 4000; break; - case 4: real_rate = 4410; break; - case 5: real_rate = 4800; break; - case 6: real_rate = 5525; break; - case 7: real_rate = 6000; break; - case 8: real_rate = 6615; break; - case 9: real_rate = 7200; break; - } - - statics.setDatarate(real_rate); + if (cb_speed.Text.Contains("3000")) statics.real_datarate = 3000; + if (cb_speed.Text.Contains("4000")) statics.real_datarate = 4000; + if (cb_speed.Text.Contains("4410")) statics.real_datarate = 4410; + if (cb_speed.Text.Contains("4800")) statics.real_datarate = 4800; + if (cb_speed.Text.Contains("5500")) statics.real_datarate = 5500; + if (cb_speed.Text.Contains("6000")) statics.real_datarate = 6000; + if (cb_speed.Text.Contains("6600")) statics.real_datarate = 6600; + if (cb_speed.Text.Contains("7200")) statics.real_datarate = 7200; Byte[] txdata = new byte[statics.PayloadLen + 2]; + int idx = cb_speed.SelectedIndex; txdata[0] = (Byte)statics.ResamplingRate; // BER Test Marker txdata[1] = (Byte)idx; @@ -1307,7 +1018,6 @@ namespace oscardata // stop any ongoing transmission button_cancelimg_Click(null, null); } - private void timer_searchmodem_Tick(object sender, EventArgs e) { @@ -1415,6 +1125,9 @@ namespace oscardata s = ReadString(sr); cb_autostart.Checked = (s == "1"); try { cb_announcement.Text = ReadString(sr); } catch { } + s = ReadString(sr); + try { cb_stampinfo.Checked = (s == "1"); } catch { } + try { tb_info.Text = ReadString(sr); } catch { } } } catch @@ -1443,7 +1156,8 @@ namespace oscardata sw.WriteLine(tb_CAPvol.Value.ToString()); sw.WriteLine(cb_autostart.Checked ? "1" : "0"); sw.WriteLine(cb_announcement.Text); - + sw.WriteLine(cb_stampinfo.Checked ? "1" : "0"); + sw.WriteLine(tb_info.Text); } } catch { } diff --git a/oscardata/oscardata/bin/Release/oscardata.exe b/oscardata/oscardata/bin/Release/oscardata.exe index f635a3e..33a23b4 100755 Binary files a/oscardata/oscardata/bin/Release/oscardata.exe and b/oscardata/oscardata/bin/Release/oscardata.exe differ diff --git a/oscardata/oscardata/config.cs b/oscardata/oscardata/config.cs index 237da65..621e4d0 100755 --- a/oscardata/oscardata/config.cs +++ b/oscardata/oscardata/config.cs @@ -54,7 +54,6 @@ namespace oscardata public static int UdpBlocklen = 258; // length of a data block (UDP) public static int PayloadLen = UdpBlocklen - FecLen - 3 - 2 - 2; public static int real_datarate = 6000; // speed in bit/s - static int datarate = (real_datarate * 100) / 99; // little bit more to avoid underruns public static String jpg_tempfilename = "rxdata.jpg"; public static String zip_TXtempfilename = "TXtemp.zip"; public static String zip_RXtempfilename = "RXtemp.zip"; @@ -67,16 +66,6 @@ namespace oscardata public static String[] AudioCAPdevs; public static int PBfifousage = 0; - public static void setDatarate(int rate) - { - real_datarate = rate; - datarate = (rate * 100) / 99; - } - - public static int getDatarate() - { - return datarate; - } public static String[] getOwnIPs() { diff --git a/oscardata/oscardata/imagehandler.cs b/oscardata/oscardata/imagehandler.cs index 2baaee4..debb729 100755 --- a/oscardata/oscardata/imagehandler.cs +++ b/oscardata/oscardata/imagehandler.cs @@ -57,7 +57,7 @@ namespace oscardata return 5; } - public Bitmap ResizeImage(Image image, int width, int height, String callsign) + public Bitmap ResizeImage(Image image, int width, int height, String callsign, String info) { // get original size of img int x = image.Width; @@ -90,6 +90,19 @@ namespace oscardata g.DrawString(callsign, fnt, Brushes.Blue, 5, 5); } } + if (info != "") + { + using (var fnt = new Font("Verdana", 11.0f)) + { + int ypos = nh - 30; + var size = g.MeasureString(info, fnt); + var rect = new RectangleF(5, ypos, size.Width, size.Height); + SolidBrush opaqueBrush = new SolidBrush(Color.FromArgb(128, 255, 255, 255)); + + g.FillRectangle(opaqueBrush, rect); + g.DrawString(info, fnt, Brushes.Blue, 5, ypos); + } + } } return destImage; diff --git a/oscardata/oscardata/oscardata.csproj b/oscardata/oscardata/oscardata.csproj index bed1928..189713b 100755 --- a/oscardata/oscardata/oscardata.csproj +++ b/oscardata/oscardata/oscardata.csproj @@ -67,6 +67,7 @@ + diff --git a/oscardata/oscardata/receivefile.cs b/oscardata/oscardata/receivefile.cs new file mode 100755 index 0000000..565685a --- /dev/null +++ b/oscardata/oscardata/receivefile.cs @@ -0,0 +1,533 @@ +/* +* High Speed modem to transfer data in a 2,7kHz SSB channel +* ========================================================= +* Author: DJ0ABR +* +* (c) DJ0ABR +* www.dj0abr.de +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation; either version 2 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +* +* File restauration process +* ========================= +* 1) file header is received, get filename and file ID (which is the CRC16 over the complete file contents) +* 2) is this file already existing ? yes-> 3) +* 3) this file does not exist -> 4) +* +* 3) file with this name and ID exists is already, cancel reception, show file +* +* 4) file dows not exist, receive the blocks +* 5) a block is missing -> find a previous block-file with this name+ID. If exists, take the block from this file +* 6) reception complete, the files is OK -> save the file, delete a block file +* 7) reception complete, the files is incomplete -> save the block file for late use +* +* Filenames: +* ---------- +* transmitter: +* the sender takes any filename, but the ID is the C&C16 over the file contents (to identify different files with the same name) +* +* receiver: +* use the filename as transmitted for the good original file +* the blockfile's filename is the ID in Ascii-Hex representation, i.e.: ID= 0x45 0xfe ... filename is: 45FE.blk +*/ + +using System; +using System.Drawing; +using System.IO; + +namespace oscardata +{ + class receivefile + { + int rxtype; + int rxfrmnum; + int minfo; + int rxstat; + int speed; + int maxlevel; + int dummy4; + int dummy5; + Byte[] rxdata = new byte[statics.PayloadLen]; + + // file buffer, we have max 2^10=1024 blocks with 219 bytes each = 224.256kB + Byte[,] blockbuf = new byte[1024, statics.PayloadLen]; + bool[] blockvalid = new bool[1024]; + int blockidx; + Byte[] firstblock; + + bool receiving = false; + public String filename = null; + public String StatusText = ""; + public long filesize = 0; + public Bitmap pbmp = null; + DateTime starttime; + public TimeSpan runtime; + public int rxbytes = 0; + bool autoRXnum = false; + String blockFilename = ""; + + public bool receive(Byte []rxdp) + { + // read frame header + if(!getFrameHeader(rxdp)) + { + // invalid situation + blockidx = 0; + receiving = false; + return false; + } + + // receive first frame of a transmission + if (minfo == statics.FirstFrame || minfo == statics.SingleFrame) + { + starttime = DateTime.UtcNow; + rxbytes = 0; + filesize = 0; + filename = ""; + if (!StartFileRX()) return false; // invalid file + + // check if file already exists + if (fileExists()) + { + // exists already, no need to receive + filename = makeRXfilename(); + + if (rxtype == statics.Image) + { + try + { + // show existing image + Image img = Image.FromFile(filename); + pbmp = new Bitmap(img); + } + catch { pbmp = null; } + } + receiving = false; + return true; + } + } + + if (minfo != statics.FirstFrame) + runtime = DateTime.UtcNow - starttime; + + // receive continous frames of a transmission + if (minfo == statics.NextFrame) + { + // there are more frames for this file + if(!FileRX()) + { + // invalid situation + blockidx = 0; + receiving = false; + return false; + } + } + + rxbytes += statics.PayloadLen; + + // receive last file of a transmission + if (minfo == statics.LastFrame || minfo == statics.SingleFrame) + { + if (!FileRX()) + { + // invalid situation + blockidx = 0; + receiving = false; + return false; + } + + // the last block was received ok + // save file if all blocks valid + SaveFile(); + blockidx = 0; + receiving = false; + + if (rxtype == statics.AsciiFile || rxtype == statics.HTMLFile || rxtype == statics.BinaryFile) + { + // these file type must be unzipped + handleZIPfiles(); + receiving = false; + return true; + } + } + + if (rxtype == statics.Image) + { + // build bitmap from received data + pbmp = buildBitmap(); + return true; + } + + return false; + } + + bool getFrameHeader(Byte[] rxd) + { + rxtype = rxd[0]; + rxfrmnum = rxd[1]; + rxfrmnum <<= 8; + rxfrmnum += rxd[2]; + minfo = rxd[3]; + rxstat = rxd[4]; + speed = rxd[5]; + speed <<= 8; + speed += rxd[6]; + maxlevel = rxd[7]; + dummy4 = rxd[8]; + dummy5 = rxd[9]; + + if (rxfrmnum >= 1024) return false; + if (!autoRXnum) + blockidx = rxfrmnum; + + Array.Copy(rxd, 10, rxdata, 0, rxd.Length - 10); + return true; + } + + bool StartFileRX() + { + if (receiving) return true; // already open + + //Console.WriteLine("first block"); + + // store first block + filename = null; + if (rxfrmnum != 0) + { + Console.WriteLine("blockidx auto increment"); + autoRXnum = true; // for old compatibility, increment blockidx by ourself + } + blockidx = 0; + if (rxdata.Length > statics.PayloadLen) + { + Console.WriteLine("wrong payload size: " + rxdata.Length + " expected:" + statics.PayloadLen); + return false; // wrong size + } + + // read file header + ArraySend.rxFilename = ""; + firstblock = ArraySend.GetAndRemoveHeader(rxdata); + if (firstblock == null) + { + Console.WriteLine("invalid File header"); + return false; // cannot read header, file is corrupted, ignore + } + + // get block filename for this file + blockFilename = idToFilename(ArraySend.FileID); + + // init valid blocks table + for(int i=0; i= 0 && h <= 9) return (char)(0x30 + h); + return (char)(0x41 + h - 10); + } + + Byte b0 = (Byte)((id >> 12) & 0x0f); + Byte b1 = (Byte)((id >> 8) & 0x0f); + Byte b2 = (Byte)((id >> 4) & 0x0f); + Byte b3 = (Byte)(id & 0x0f); + + String s = ""; + s += hexToChar(b0); + s += hexToChar(b1); + s += hexToChar(b2); + s += hexToChar(b3); + s += ".blk"; + + return statics.getHomePath("blocks", s); + } + + public bool blockstat(int[] result) + { + if (blockidx == 0) return false; + int ok = 0; + for (int i = 0; i <= blockidx; i++) + { + if (blockvalid[i]) ok++; + } + result[0] = blockidx+1; // +1 because we start with block 0 + result[1] = ok; + + return true; + } + + void saveBlocks() + { + try + { + using (BinaryWriter writer = new BinaryWriter(File.Open(blockFilename, FileMode.Create))) + { + for (int i = 0; i <= blockidx; i++) + { + // first byte of a block: valid/invalid block + Byte[] blk = new byte[statics.PayloadLen + 1]; + for (int j = 0; j < statics.PayloadLen; j++) + blk[j + 1] = blockbuf[i, j]; + + blk[0] = blockvalid[i] ? (Byte)1 : (Byte)0; + + writer.Write(blk); + } + writer.Close(); + } + } + catch { } + } + + void readBlocks() + { + Byte[] blk = new byte[statics.PayloadLen + 1]; + int idx = 0; + + try + { + using (BinaryReader reader = new BinaryReader(File.Open(blockFilename, FileMode.Open))) + { + while (true) + { + int ret = reader.Read(blk, 0, statics.PayloadLen + 1); + if (ret != statics.PayloadLen + 1) break; + + for (int j = 0; j < statics.PayloadLen; j++) + { + if (blk[0] == 1) + { + blockbuf[idx, j] = blk[j + 1]; + blockvalid[idx] = true; + } + } + idx++; + } + reader.Close(); + } + } + catch { } + } + + bool FileRX() + { + if (!receiving) + { + Console.WriteLine("next/last block: not receiving, first block missing?"); + return false; + } + + //Console.WriteLine("next/last block"); + + // store next block + if (rxdata.Length != statics.PayloadLen) + { + Console.WriteLine("wrong payload size: " + rxdata.Length + " expected:" + statics.PayloadLen); + return false; // wrong size + } + + if (autoRXnum) + { + blockidx++; + Console.WriteLine("blockidx: do auto increment " + blockidx); + } + + for (int i = 0; i < rxdata.Length; i++) + blockbuf[blockidx, i] = rxdata[i]; + + blockvalid[blockidx] = true; + + return true; + } + + // build full path+filename from received filename + String makeRXfilename() + { + String fn = ""; + // remove possible path from filename + String fname = ArraySend.rxFilename; + int idx = fname.IndexOfAny(new char[] { '\\', '/' }); + if (idx != -1) + { + try + { + fname = fname.Substring(idx + 1); + } + catch { } + } + if (rxtype == statics.Image) + fn = statics.getHomePath(statics.RXimageStorage, fname); + else + fn = statics.getHomePath("", fname); + return fn; + } + + bool fileExists() + { + String fn = makeRXfilename(); + if (!File.Exists(fn)) return false; + + // File exists, but is the ID the same ? + Byte[] ba = File.ReadAllBytes(fn); + if (ba == null) return false; + Crc c = new Crc(); + int fncrc = c.crc16_messagecalc(ba, ba.Length); + if (ArraySend.FileID != fncrc) return false; + return true; + } + + bool SaveFile() + { + Console.WriteLine("save file"); + + // check if all blocks ok + for (int i = 0; i <= blockidx; i++) + { + if (blockvalid[i] == false) + { + Console.WriteLine("save file: not all blocks ok"); + // save block file + saveBlocks(); + return false; + } + } + + // reception was ok, blockfile no more needed, delete it + // file is OK, blockfile no more needed, delete it + try { File.Delete(blockFilename); } catch { } + + // make filename + filename = makeRXfilename(); + + Console.WriteLine("save at " + filename); + + using (BinaryWriter writer = new BinaryWriter(File.Open(filename, FileMode.Create))) + { + for (int i = 0; i <= blockidx; i++) + { + if (i == 0) + writer.Write(firstblock); + else + { + Byte[] blk = new byte[statics.PayloadLen]; + for (int j = 0; j < statics.PayloadLen; j++) + blk[j] = blockbuf[i, j]; + + writer.Write(blk); + } + } + writer.Close(); + } + + return true; + } + + Bitmap buildBitmap() + { + Bitmap bmp = null; + + using (MemoryStream memStream = new MemoryStream()) + { + for (int i = 0; i <= blockidx; i++) + { + if (i == 0) + memStream.Write(firstblock, 0, firstblock.Length); + else + { + Byte[] blk = new byte[statics.PayloadLen]; + for (int j = 0; j < statics.PayloadLen; j++) + blk[j] = blockbuf[i, j]; + + memStream.Write(blk, 0, blk.Length); + } + } + try + { + // first assign it to an "Image", this checks if the image is valid + Image img = Image.FromStream(memStream); + bmp = new Bitmap(img); + } + catch + { + return null; + } + } + + return bmp; + } + + void handleZIPfiles() + { + if (filename == null) + { + Console.WriteLine("handleZIPfile: no filename"); + return; + } + + // filename has the received data, but maybe too long (multiple of payload length) + // reduce for the real file length + Byte[] fc = File.ReadAllBytes(filename); + Byte[] fdst = new byte[ArraySend.FileSize]; + if (fc.Length < ArraySend.FileSize) + { + Console.WriteLine("file not complete: got len=" + fc.Length + " expected len=" + ArraySend.FileSize); + return; + } + Array.Copy(fc, 0, fdst, 0, ArraySend.FileSize); + File.WriteAllBytes(statics.zip_RXtempfilename, fdst); // the received file (still zipped) is here + + // unzip received data and store result in file: unzipped_RXtempfilename + ZipStorer zs = new ZipStorer(); + String fl = zs.unzipFile(statics.zip_RXtempfilename); + if (fl != null) + { + // save file + // fl is the filename of the file inside the zip file, so the originally zipped file + // remove path to get just the filename + int idx = fl.LastIndexOf('/'); + if (idx == -1) idx = fl.LastIndexOf('\\'); + String fdest = fl.Substring(idx + 1); + fdest = statics.getHomePath("", fdest); + // fdest is the file in the oscardata's user home directoty + // remove old file with same name + try { File.Delete(fdest); } catch { } + // move the unzipped file to the final location + File.Move(fl, fdest); + filesize = statics.GetFileSize(fdest); + StatusText = "unzip OK"; + } + else + StatusText = "unzip failed"; + + File.Delete(statics.zip_RXtempfilename); + } + } +}