/* * High Speed modem to transfer data in a 2,7kHz SSB channel * ========================================================= * Author: DJ0ABR * made for: AMSAT-DL * * (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. * */ /* * this is a console program * it can be compiled under Linux: make * and under Windows: Visual-Studio * * 3rd party libraries: * 1) BASS Audio from https://www.un4seen.com/ copy bass.h and bass.lib into source directory Windows: copy bass.dll into executable directory Linux: copy libbass.so into shared-lib folder, usually /usr/local/lib ! NOTE: for PC-Linux and ARM-Linux you need different libraries ! 2) liquid-DSP Linux Install Script: this installs it from source sudo apt install git autoconf libsndfile-dev libasound-dev git clone git://github.com/jgaeddert/liquid-dsp.git cd liquid-dsp ./bootstrap.sh ./configure make -j 8 sudo make install sudo ldconfig a working copy of the source code is in ../3rdParty/liquid-dsp to use this source simply remove the "git clone" line from above script it installs libliquid.so into /usr/local/lib (Ubuntu) and liquid.h into /usr/local/include/liquid/ Windows: ready libraries are in ../3rdParty/liquid-dsp-windows copy liquid.h and liquid.lib into source directory copy liquid.dll into executable directory */ #include "hsmodem.h" void toGR_sendData(uint8_t* data, int type, int status); void bc_rxdata(uint8_t* pdata, int len, struct sockaddr_in* rxsock); void appdata_rxdata(uint8_t* pdata, int len, struct sockaddr_in* rxsock); void startModem(); // threads will exit if set to 0 int keeprunning = 1; // UDP I/O int BC_sock_AppToModem = -1; int DATA_sock_AppToModem = -1; int DATA_sock_from_GR = -1; int DATA_sock_FFT_from_GR = -1; int DATA_sock_I_Q_from_GR = -1; int UdpBCport_AppToModem = 40131; int UdpDataPort_AppToModem = 40132; int UdpDataPort_ModemToApp = 40133; int UdpDataPort_toGR = 40134; int UdpDataPort_fromGR = 40135; int UdpDataPort_fromGR_FFT = 40136; int UdpDataPort_fromGR_I_Q = 40137; // op mode depending values // default mode if not set by the app int speedmode = 7; int bitsPerSymbol = 2; // QPSK=2, 8PSK=3 int constellationSize = 4; // QPSK=4, 8PSK=8 char localIP[] = { "127.0.0.1" }; char ownfilename[] = { "hsmodem" }; char appIP[20] = { 0 }; int fixappIP = 0; int restart_modems = 0; int caprate = 44100; int txinterpolfactor = 20; int rxPreInterpolfactor = 5; int captureDeviceNo = -1; int playbackDeviceNo = -1; int main(int argc, char* argv[]) { int opt = 0; char* modemip = NULL; #ifdef _LINUX_ while ((opt = getopt(argc, argv, "m:")) != -1) { switch (opt) { case 'm': // specify IP of application: hsmodem -m 192.168.0.1 modemip = optarg; memset(appIP, 0, 20); int len = strlen(modemip); if (len < 16) { memcpy(appIP, modemip, len); fixappIP = 1; printf("Application IP set to: %s\n", modemip); } else { printf("invalid Application IP: %s\n", modemip); exit(0); } break; } } if (isRunning(ownfilename) == 1) exit(0); install_signal_handler(); #endif #ifdef _WIN32_ if (argc != 1 && argc != 3) { printf("invalid argument\n"); exit(0); } if (argc == 3) { memset(appIP, 0, 20); int len = strlen(argv[2]); if (len < 16) { memcpy(appIP, argv[2], len); fixappIP = 1; printf("Application IP set to: %s\n", argv[2]); } else { printf("invalid Application IP: %s\n", modemip); exit(0); } } #endif init_packer(); initFEC(); init_fft(); int ar = init_audio(playbackDeviceNo, captureDeviceNo); if (ar == -1) { keeprunning = 0; exit(0); } // start udp RX to listen for broadcast search message from Application UdpRxInit(&BC_sock_AppToModem, UdpBCport_AppToModem, &bc_rxdata, &keeprunning); // start udp RX for data from application UdpRxInit(&DATA_sock_AppToModem, UdpDataPort_AppToModem, &appdata_rxdata, &keeprunning); // start udp RX to listen for data from GR Receiver UdpRxInit(&DATA_sock_from_GR, UdpDataPort_fromGR, &GRdata_rxdata, &keeprunning); printf("QO100modem initialised and running\n"); while (keeprunning) { if (restart_modems == 1) { startModem(); restart_modems = 0; } //doArraySend(); if (demodulator() == 0) sleep_ms(100); } printf("stopped: %d\n", keeprunning); #ifdef _LINUX_ close(BC_sock_AppToModem); #endif #ifdef _WIN32_ closesocket(BC_sock_AppToModem); #endif return 0; } typedef struct { int audio; int tx; int rx; int bpsym; } SPEEDRATE; SPEEDRATE sr[10] = { // QPSK modes {48000, 32, 8, 2}, // AudioRate, TX-Resampler, RX-Resampler/4, bit/symbol {44100, 28, 7, 2}, // see samprate.ods {44100, 24, 6, 2}, {48000, 24, 6, 2}, {44100, 20, 5, 2}, {48000, 20, 5, 2}, // 8PSK modes {44100, 24, 6, 3}, {48000, 24, 6, 3}, {44100, 20, 5, 3}, {48000, 20, 5, 3} }; void startModem() { bitsPerSymbol = sr[speedmode].bpsym; constellationSize = (1 << bitsPerSymbol); // QPSK=4, 8PSK=8 caprate = sr[speedmode].audio; txinterpolfactor = sr[speedmode].tx; rxPreInterpolfactor = sr[speedmode].rx; // int TX audio and modulator close_dsp(); init_audio(playbackDeviceNo, captureDeviceNo); init_dsp(); } void setAudioDevices(int pb, int cap) { //printf("%d %d\n", pb, cap); if (pb != playbackDeviceNo || cap != captureDeviceNo) { restart_modems = 1; playbackDeviceNo = pb; captureDeviceNo = cap; } } // called from UDP RX thread for Broadcast-search from App void bc_rxdata(uint8_t* pdata, int len, struct sockaddr_in* rxsock) { if (len > 0 && pdata[0] == 0x3c) { setAudioDevices(pdata[1], pdata[2]); char rxip[20]; strcpy(rxip, inet_ntoa(rxsock->sin_addr)); if (fixappIP == 0) { if (strcmp(appIP, rxip)) { printf("new app IP: %s, restarting modems\n", rxip); restart_modems = 1; } strcpy(appIP, rxip); //printf("app (%s) is searching modem. Sending modem IP to the app\n",appIP); // App searches for the modem IP, mirror the received messages // so the app gets an UDP message with this local IP int alen; uint8_t* txdata = getAudioDevicelist(&alen); sendUDP(appIP, UdpDataPort_ModemToApp, txdata, alen); } else { // appIP is fixed, answer only to this IP if (!strcmp(appIP, rxip)) { //printf("app (%s) is searching modem. Sending modem IP to the app\n",appIP); restart_modems = 1; // App searches for the modem IP, mirror the received messages // so the app gets an UDP message with this local IP int alen; uint8_t* txdata = getAudioDevicelist(&alen); sendUDP(appIP, UdpDataPort_ModemToApp, txdata, alen); } } } } // called by UDP RX thread for data from App void appdata_rxdata(uint8_t* pdata, int len, struct sockaddr_in* rxsock) { uint8_t type = pdata[0]; uint8_t minfo = pdata[1]; if (len != (PAYLOADLEN + 2)) { printf("data from app: wrong length:%d (should be %d)\n", len - 2, PAYLOADLEN); return; } // type values: see oscardata config.cs: frame types if (type == 16) { // Byte 1 contains the resampler ratio for TX and RX modem speedmode = pdata[1]; printf("set speedmode to %d\n", speedmode); restart_modems = 1; return; } if (type == 17) { // auto send file // TODO // for testing only: // simulate sending a text file with 1kB length /*int testlen = 100000; uint8_t arr[100000]; char c = 'A'; for (int i = 0; i < testlen; i++) { arr[i] = c; if (++c > 'Z') c = 'A'; } arraySend(arr, testlen, 3, (char*)"testfile.txt");*/ return; } if (type == 18) { // auto send folder // TODO } if (type == 19) { // shut down this modem PC int r = system("sudo shutdown now"); exit(r); } if (type == 20) { // reset liquid RX modem resetModem(); } //if (getSending() == 1) return; // already sending (Array sending) if (minfo == 0) { // this is the first frame of a larger file // send it multiple times, like a preamble, to give the // receiver some time for synchronisation // duration: 3 seconds // 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 = 3 * ((caprate / txinterpolfactor) * bitsPerSymbol / 8) / 258 + 1; for (int i = 0; i < numframespreamble; i++) toGR_sendData(pdata + 2, type, minfo); } else if ((len - 2) < PAYLOADLEN) { // if not enough data for a full payload add Zeros uint8_t payload[PAYLOADLEN]; memset(payload, 0, PAYLOADLEN); memcpy(payload, pdata + 2, len - 2); toGR_sendData(payload, type, minfo); } else { toGR_sendData(pdata + 2, type, minfo); } } void toGR_sendData(uint8_t* data, int type, int status) { int len = 0; uint8_t* txdata = Pack(data, type, status, &len); //showbytestring((char *)"BERtx: ", txdata, len); if (txdata != NULL) sendToModulator(txdata, len); } // called by UDP RX thread or liquid demodulator for received data void GRdata_rxdata(uint8_t* pdata, int len, struct sockaddr_in* rxsock) { static int fnd = 0; // raw symbols uint8_t* pl = unpack_data(pdata, len); if (pl != NULL) { // complete frame received // send payload to app uint8_t txpl[PAYLOADLEN + 10 + 1]; memcpy(txpl + 1, pl, PAYLOADLEN + 10); txpl[0] = 1; // type 1: payload data follows sendUDP(appIP, UdpDataPort_ModemToApp, txpl, PAYLOADLEN + 10 + 1); fnd = 0; } else { // no frame found // if longer ws seconds nothing found, reset liquid RX modem // comes here with symbol rate, i.e. 4000 S/s int ws = 2; int wt = sr[speedmode].audio / sr[speedmode].tx; if (++fnd >= (wt * ws)) { fnd = 0; //printf("no signal detected %d, reset RX modem\n", wt); resetModem(); } } }