#include #include #include "Sonde.h" #include "RS41.h" #include "RS92.h" #include "DFM.h" #include "SX1278FSK.h" #include "Display.h" extern U8X8_SSD1306_128X64_NONAME_SW_I2C *u8x8; extern SX1278FSK sx1278; RXTask rxtask = { -1, -1, -1, -1, 0 }; const char *evstring[]={"NONE", "KEY1S", "KEY1D", "KEY1M", "KEY1L", "KEY2S", "KEY2D", "KEY2M", "KEY2L", "VIEWTO", "RXTO", "NORXTO", "(max)"}; const char *RXstr[]={"RX_OK", "RX_TIMEOUT", "RX_ERROR", "RX_UNKNOWN"}; /* Task model: * There is a background task for all SX1278 interaction. * - On startup and on each mode/frequency change (requested by setting requestNextSonde * to an sonde index >=0) it calls Sonde::setup(), which will call the new decoder's * setup function. Setup will update the value currentSonde. * - Periodically it calls Sonde::receive(), which calls the current decoder's receive() * function. It should return control to the SX1278 main loop at least once per second. * It will also set the internal variable receiveResult. The decoder's receive function * must make sure that there are no FIFI overflows in the SX1278. * - the Arduino main loop will call the waitRXcomplete function, which should return as * soon as there is some new data to display, or no later than after 1s, returning the * value of receiveResult (or timeout, if receiveResult was not set within 1s). It * should also return immediately if there is some keyboard input. */ Sonde::Sonde() { config.button_pin = 0; config.button2_pin = T4 + 128; // T4 == GPIO13, should be ok for v1 and v2 config.touch_thresh = 60; config.led_pout = 9; // Try autodetecting board type // Seems like on startup, GPIO4 is 1 on v1 boards, 0 on v2.1 boards? int autodetect = gpio_get_level((gpio_num_t)4); if(autodetect==1) { config.oled_sda = 4; config.oled_scl = 15; } else { config.oled_sda = 21; config.oled_scl = 22; } // config.oled_rst = 16; config.noisefloor = -125; strcpy(config.call,"NOCALL"); strcpy(config.passcode, "---"); config.maxsonde=15; config.debug=0; config.wifi=1; config.wifiap=1; config.display=1; config.startfreq=400; config.channelbw=10; config.spectrum=10; config.timer=0; config.marker=0; config.showafc=0; config.freqofs=0; config.rs41.agcbw=25000; config.rs41.rxbw=12000; config.udpfeed.active = 1; config.udpfeed.type = 0; strcpy(config.udpfeed.host, "192.168.42.20"); strcpy(config.udpfeed.symbol, "/O"); config.udpfeed.port = 9002; config.udpfeed.highrate = 1; config.udpfeed.idformat = ID_DFMGRAW; config.tcpfeed.active = 0; config.tcpfeed.type = 1; strcpy(config.tcpfeed.host, "radiosondy.info"); strcpy(config.tcpfeed.symbol, "/O"); config.tcpfeed.port = 12345; config.tcpfeed.highrate = 10; config.tcpfeed.idformat = ID_DFMDXL; } void Sonde::setConfig(const char *cfg) { while(*cfg==' '||*cfg=='\t') cfg++; if(*cfg=='#') return; char *s = strchr(cfg,'='); if(!s) return; char *val = s+1; *s=0; s--; while(s>cfg && (*s==' '||*s=='\t')) { *s=0; s--; } Serial.printf("configuration option '%s'=%s \n", cfg, val); if(strcmp(cfg,"noisefloor")==0) { config.noisefloor = atoi(val); if(config.noisefloor==0) config.noisefloor=-130; } else if(strcmp(cfg,"call")==0) { strncpy(config.call, val, 9); } else if(strcmp(cfg,"passcode")==0) { strncpy(config.passcode, val, 9); } else if(strcmp(cfg,"button_pin")==0) { config.button_pin = atoi(val); } else if(strcmp(cfg,"button2_pin")==0) { config.button2_pin = atoi(val); } else if(strcmp(cfg,"touch_thresh")==0) { config.touch_thresh = atoi(val); } else if(strcmp(cfg,"led_pout")==0) { config.led_pout = atoi(val); } else if(strcmp(cfg,"oled_sda")==0) { config.oled_sda = atoi(val); } else if(strcmp(cfg,"oled_scl")==0) { config.oled_scl = atoi(val); } else if(strcmp(cfg,"oled_rst")==0) { config.oled_rst = atoi(val); } else if(strcmp(cfg,"maxsonde")==0) { config.maxsonde = atoi(val); if(config.maxsonde>MAXSONDE) config.maxsonde=MAXSONDE; } else if(strcmp(cfg,"debug")==0) { config.debug = atoi(val); } else if(strcmp(cfg,"wifi")==0) { config.wifi = atoi(val); } else if(strcmp(cfg,"wifiap")==0) { config.wifiap = atoi(val); } else if(strcmp(cfg,"display")==0) { config.display = atoi(val); disp.setLayout(config.display); } else if(strcmp(cfg,"startfreq")==0) { config.startfreq = atoi(val); } else if(strcmp(cfg,"channelbw")==0) { config.channelbw = atoi(val); } else if(strcmp(cfg,"spectrum")==0) { config.spectrum = atoi(val); } else if(strcmp(cfg,"timer")==0) { config.timer = atoi(val); } else if(strcmp(cfg,"marker")==0) { config.marker = atoi(val); } else if(strcmp(cfg,"showafc")==0) { config.showafc = atoi(val); } else if(strcmp(cfg,"freqofs")==0) { config.freqofs = atoi(val); } else if(strcmp(cfg,"rs41.agcbw")==0) { config.rs41.agcbw = atoi(val); } else if(strcmp(cfg,"rs41.rxbw")==0) { config.rs41.rxbw = atoi(val); } else if(strcmp(cfg,"axudp.active")==0) { config.udpfeed.active = atoi(val)>0; } else if(strcmp(cfg,"axudp.host")==0) { strncpy(config.udpfeed.host, val, 63); } else if(strcmp(cfg,"axudp.port")==0) { config.udpfeed.port = atoi(val); } else if(strcmp(cfg,"axudp.symbol")==0) { strncpy(config.udpfeed.symbol, val, 3); } else if(strcmp(cfg,"axudp.highrate")==0) { config.udpfeed.highrate = atoi(val); } else if(strcmp(cfg,"axudp.idformat")==0) { config.udpfeed.idformat = atoi(val); } else if(strcmp(cfg,"tcp.active")==0) { config.tcpfeed.active = atoi(val)>0; } else if(strcmp(cfg,"tcp.host")==0) { strncpy(config.tcpfeed.host, val, 63); } else if(strcmp(cfg,"tcp.port")==0) { config.tcpfeed.port = atoi(val); } else if(strcmp(cfg,"tcp.symbol")==0) { strncpy(config.tcpfeed.symbol, val, 3); } else if(strcmp(cfg,"tcp.highrate")==0) { config.tcpfeed.highrate = atoi(val); } else if(strcmp(cfg,"tcp.idformat")==0) { config.tcpfeed.idformat = atoi(val); } else { Serial.printf("Invalid config option '%s'=%s \n", cfg, val); } } void Sonde::clearIP() { disp.clearIP(); } void Sonde::setIP(const char *ip, bool AP) { disp.setIP(ip, AP); } void Sonde::clearSonde() { nSonde = 0; } void Sonde::addSonde(float frequency, SondeType type, int active, char *launchsite) { if(nSonde>=config.maxsonde) { Serial.println("Cannot add another sonde, MAXSONDE reached"); return; } Serial.printf("Adding %f - %d - %d - %s\n", frequency, type, active, launchsite); sondeList[nSonde].type = type; sondeList[nSonde].freq = frequency; sondeList[nSonde].active = active; strncpy(sondeList[nSonde].launchsite, launchsite, 17); memcpy(sondeList[nSonde].rxStat, "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3", 18); // unknown/undefined nSonde++; } // called by updateState (only) void Sonde::nextConfig() { currentSonde++; if(currentSonde>=nSonde) { currentSonde=0; } // Skip non-active entries (but don't loop forever if there are no active ones) for(int i=0; i=nSonde) currentSonde=0; } } } void Sonde::nextRxSonde() { rxtask.currentSonde++; if(rxtask.currentSonde>=nSonde) { rxtask.currentSonde=0; } for(int i=0; i=nSonde) rxtask.currentSonde=0; } } Serial.printf("nextRxSonde: %d\n", rxtask.currentSonde); } SondeInfo *Sonde::si() { return &sondeList[currentSonde]; } void Sonde::setup() { if(rxtask.currentSonde<0 || rxtask.currentSonde>=config.maxsonde) { Serial.print("Invalid rxtask.currentSonde: "); Serial.println(rxtask.currentSonde); rxtask.currentSonde = 0; } // TODO: maybe better done in arduino task, not in rx task sondeList[rxtask.currentSonde].lastState = -1; sondeList[rxtask.currentSonde].viewStart = millis(); // update receiver config Serial.print("\nSonde::setup() on sonde index "); Serial.println(rxtask.currentSonde); switch(sondeList[rxtask.currentSonde].type) { case STYPE_RS41: rs41.setup(sondeList[rxtask.currentSonde].freq * 1000000); break; case STYPE_DFM06: case STYPE_DFM09: dfm.setup( sondeList[rxtask.currentSonde].freq * 1000000, sondeList[rxtask.currentSonde].type==STYPE_DFM06?0:1 ); break; case STYPE_RS92: rs92.setup( sondeList[rxtask.currentSonde].freq * 1000000); } // debug float afcbw = sx1278.getAFCBandwidth(); float rxbw = sx1278.getRxBandwidth(); Serial.printf("AFC BW: %f RX BW: %f\n", afcbw, rxbw); } void Sonde::receive() { uint16_t res; SondeInfo *si = &sondeList[rxtask.currentSonde]; switch(si->type) { case STYPE_RS41: res = rs41.receive(); break; case STYPE_RS92: res = rs92.receive(); break; case STYPE_DFM06: case STYPE_DFM09: res = dfm.receive(); break; } // state information for RX_TIMER / NORX_TIMER events if(res==0) { // RX OK if(si->lastState != 1) { si->rxStart = millis(); si->lastState = 1; } } else { // RX not ok if(si->lastState != 0) { si->norxStart = millis(); si->lastState = 0; } } // we should handle timer events here, because after returning from receive, // we'll directly enter setup int event = timeoutEvent(); int action = disp.layout->actions[event]; if(action == ACT_NEXTSONDE || action==ACT_PREVSONDE) { // handled here... nextRxSonde(); rxtask.requestSonde = rxtask.currentSonde; res = 0xFF00 | res; } else { res = (action<<8) | res; } // let waitRXcomplete resume... rxtask.receiveResult = res; } // return (action<<8) | (rxresult) uint16_t Sonde::waitRXcomplete() { uint16_t res=0; uint32_t t0 = millis(); while( rxtask.receiveResult==0xFFFF && millis()-t0 < 2000) { delay(50); } if( rxtask.receiveResult==0xFFFF) { res = RX_TIMEOUT; } else { res = rxtask.receiveResult; } rxtask.receiveResult = 0xFFFF; Serial.printf("waitRXcomplete returning %04x (%s)\n", res, RXstr[res&0xff]); #if 0 //currently not used... { int res; switch(sondeList[rxtask.currentSonde].type) { case STYPE_RS41: res = rs41.waitRXcomplete(); break; case STYPE_RS92: res = rs92.waitRXcomplete(); break; case STYPE_DFM06: case STYPE_DFM09: res = dfm.waitRXcomplete(); break; } #endif memmove(sonde.si()->rxStat+1, sonde.si()->rxStat, 17); sonde.si()->rxStat[0] = res; return res; } uint8_t Sonde::timeoutEvent() { uint32_t now = millis(); #if 1 Serial.printf("Timeout check: %ld - %ld vs %ld; %ld - %ld vs %ld; %ld - %ld vs %ld\n", now, sonde.si()->viewStart, disp.layout->timeouts[0], now, sonde.si()->rxStart, disp.layout->timeouts[1], now, sonde.si()->norxStart, disp.layout->timeouts[2]); #endif Serial.printf("lastState is %d\n", sonde.si()->lastState); if(disp.layout->timeouts[0]>=0 && now - sonde.si()->viewStart >= disp.layout->timeouts[0]) { Serial.println("View timer expired"); return EVT_VIEWTO; } if(sonde.si()->lastState==1 && disp.layout->timeouts[1]>=0 && now - sonde.si()->rxStart >= disp.layout->timeouts[1]) { Serial.println("RX timer expired"); return EVT_RXTO; } if(sonde.si()->lastState==0 && disp.layout->timeouts[2]>=0 && now - sonde.si()->norxStart >= disp.layout->timeouts[2]) { Serial.println("NORX timer expired"); return EVT_NORXTO; } return 0; } int Sonde::updateState(int8_t event) { Serial.printf("Sonde::updateState for event %d\n", event); if(event==ACT_NONE) return -1; if(event==ACT_NEXTSONDE) { sonde.nextConfig(); Serial.printf("advancing to next sonde %d\n", sonde.currentSonde); return event; } if (event==ACT_PREVSONDE) { // TODO Serial.printf("previous not supported, advancing to next sonde\n"); sonde.nextConfig(); return ACT_NEXTSONDE; } if (event==ACT_DISPLAY_SPECTRUM || event==ACT_DISPLAY_WIFI) { return event; } int n = event; if(event==ACT_DISPLAY_DEFAULT) { n = config.display; } if(n>=0&&n<4) { disp.setLayout(n); clearDisplay(); updateDisplay(); return ACT_NEXTSONDE; } return -1; } void Sonde::updateDisplayPos() { disp.updateDisplayPos(); } void Sonde::updateDisplayPos2() { disp.updateDisplayPos2(); } void Sonde::updateDisplayID() { disp.updateDisplayID(); } void Sonde::updateDisplayRSSI() { disp.updateDisplayRSSI(); } void Sonde::updateStat() { disp.updateStat(); } void Sonde::updateDisplayRXConfig() { disp.updateDisplayRXConfig(); } void Sonde::updateDisplayIP() { disp.updateDisplayIP(); } // Probing RS41 // 40x.xxx MHz void Sonde::updateDisplayScanner() { disp.setLayout(0); disp.updateDisplay(); disp.setLayout(config.display); #if 0 char buf[16]; u8x8->setFont(u8x8_font_7x14_1x2_r); u8x8->drawString(0, 0, "Scan:"); u8x8->drawString(8, 0, sondeTypeStr[si()->type]); snprintf(buf, 16, "%3.3f MHz", si()->freq); u8x8->drawString(0,3, buf); snprintf(buf, 16, "%s", si()->launchsite); u8x8->drawString(0,5, buf); updateDisplayIP(); #endif } void Sonde::updateDisplay() { disp.updateDisplay(); } void Sonde::clearDisplay() { u8x8->clearDisplay(); } Sonde sonde = Sonde();