From 6abd6a7a1a8defa969543b19979dab1c0fdcd4d1 Mon Sep 17 00:00:00 2001 From: "Hansi, dl9rdz" Date: Sun, 9 Jun 2019 23:23:43 +0200 Subject: [PATCH] display layout easier to modify --- RX_FSK/data/screens.txt | 143 ++++++++++++++++++ RX_FSK/version.h | 2 +- libraries/SondeLib/Display.cpp | 268 +++++++++++++++++++++++++++++++-- libraries/SondeLib/Display.h | 6 + libraries/SondeLib/RS41.cpp | 5 +- libraries/SondeLib/Sonde.cpp | 10 +- libraries/SondeLib/rs92gps.cpp | 1 - 7 files changed, 412 insertions(+), 23 deletions(-) create mode 100644 RX_FSK/data/screens.txt diff --git a/RX_FSK/data/screens.txt b/RX_FSK/data/screens.txt new file mode 100644 index 0000000..ffbca23 --- /dev/null +++ b/RX_FSK/data/screens.txt @@ -0,0 +1,143 @@ +# Definition of display content and action behaviour +# +# Timer: (view timer, rx timer, norx timer) +# - value -1: timer is disabled; value>=0: timer fires after (value) seconds +# - view timer: time since current view (display mode and sonde) was started +# - rx timer: time since when sonde data has been received continuously (trigger immediatly after RX) +# - norx timer: time since when no sonde data has been received continuously +# (rx and norx timer is started after tuning a new frequency and receiving a signal or not receiving +# anything for a 1s period) +# +# Actions: +# - W: activate WiFi scan +# - F: activate frequency spectrum display +# - 0: activate "Scan:" display (this is basically just display mode 0) +# - x: (1..N): activate display mode x +# - D: activate default receiver display (display mode specified in config) +# - +: advance to next active sonde from QRG config +# - #: no action +# +# Display content (lower/upper case: small/large font) +# line,column=content +# +# XText : Text +# F(suffix): frequency (with suffix, e.g., " MHz") +# L latitade +# O lOngitute +# A altitude +# H hor. speed +# V vert. speef +# Ix sonde ID (dfm format by x: d=>dxlaprs, a=>autorx, s=>real serial number) +# Q signal quality statistics bar +# T type string (RS41/DFM9/DFM6/RS92) +# C afC value +# N ip address (only tiny font) +# S launch site +# Mx telemetry value x (t temp p preassure h hyg) +# Gx value relativ to GPS reference point (x: D dist, I direction) GV. GPS valid symbol; GL, GO, GA: ref lat long alt +# R RSSI +########### +# +# Default configuration for "Scanner" display: +# - view timer disabled; rx timer=0; norx timer = 0 +# => after 1 second immediately an action is triggered +# (norx: go to next sonde; rx: go to default receiver display) +# - key1 actions: D,0,F,W +# => Button press activates default receiver view, double press does nothing +# Mid press activates Spectrum display, long press activates Wifi scan +# - key2 has no function +@Scanner:5 +timer=-1,0,0 +key1action=D,#,F,W +key2action=#,#,#,# +timeaction=#,D,+ +0,0=XScan: +0,8=T +3,0=F MHz +5,0=S +7,5=n + +############ +# Default configuration for "Legacy" display: +# - view timer=-1, rx timer=-1 (disabled); norx timer=20000 (or -1 for "old" behaviour) +# => norx timer fires after not receiving a singla for 20 seconds +# - key1 actions: +,0,F,W +# => Button1 press: next sonde; double press => @Scanner display +# => Mid press activates Spectrum display, long press activates Wifi scan +# - key2 actions: 2,#,#,# +# => BUtton2 activates display 2 (@Field) +# - timer actions: #,#,0 +# (norx timer: if no signal for >20 seconds: go back to scanner mode) +# +@Legacy:11 +timer=-1,-1,20000 +key1action=+,0,F,W +key2action=2,#,#,# +timeaction=#,#,0 +0,5=f MHz +1,8=c +0,0=t +1,0=is +2,0=L +4,0=O +2,10=a +3,10=h +4,9=v +6,0=R +6,7=Q + +############ +# Configuratoon for "Field" display (display 2) +# similar to @Legacy, but no norx timer, and Key2 goes to display 4 +@Field:7 +timer=-1,-1,-1 +key1action=+,0,F,W +key2action=3,#,#,# +timeaction=#,#,# +2,0=L +4,0=O +3,10=h +4,9=v +0,0=Is +6,0=A +6,7=Q + +############ +# Configuration for "Field2" display (display 3) +# similar to @Field +@Field2:9 +timer=-1,-1,-1 +key1action=+,0,F,W +key2action=4,#,#,# +timeaction=#,#,# +2,0=L +4,0=O +1,12=t +0,9=f +3,10=h +4,9=v +0,0=Is +6,0=A +6,7=Q + +############# +# Configuration for "GPS" display +# not yet the final version, just for testing +@GPSDIST:14 +timer=-1,-1,-1 +key1action=+,0,F,W +key2action=1,#,#,# +timeaction=#,#,# +0,0=Is +0,9=f +1,12=t +2,0=L +4,0=O +2,10=a +3,10=h +4,9=v +6,7=Q +7,0=gV +7,2=xd= +7,4=gD +7,12=gI° diff --git a/RX_FSK/version.h b/RX_FSK/version.h index 4e89fd9..aa1de6b 100644 --- a/RX_FSK/version.h +++ b/RX_FSK/version.h @@ -1,2 +1,2 @@ const char *version_name = "RDZ_TTGO_SONDE"; -const char *version_id = "devel20190608"; +const char *version_id = "devel20190609"; diff --git a/libraries/SondeLib/Display.cpp b/libraries/SondeLib/Display.cpp index 03a0b66..b4b7a7c 100644 --- a/libraries/SondeLib/Display.cpp +++ b/libraries/SondeLib/Display.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include "Display.h" @@ -56,6 +57,8 @@ static uint8_t empty_tile2[8]={0x00, 0x11, 0x02, 0x02, 0x02, 0x01, 0x00, 0x00}; static uint8_t gps_tile[8]={0x00, 0x0E, 0x1F, 0x3B, 0x71, 0x3B, 0x1F, 0x0E}; static uint8_t nogps_tile[8]={0x41, 0x22, 0x14, 0x08, 0x14, 0x22, 0x41, 0x00}; +static uint8_t deg_tile[8]={0x00, 0x06,0x09, 0x09, 0x06, 0x00, 0x00, 0x00}; + #define SETFONT(large) u8x8->setFont((large)?u8x8_font_7x14_1x2_r:u8x8_font_chroma48medium8_r); /* Description of display layouts. @@ -150,17 +153,218 @@ uint8_t gpsActions[] = { ACT_DISPLAY(1), ACT_NONE, ACT_NONE, ACT_NONE, ACT_NONE, ACT_NONE, ACT_NONE}; -DispInfo layouts[5] = { +DispInfo staticLayouts[5] = { { searchLayout, searchActions, searchTimeouts }, { legacyLayout, legacyActions, legacyTimeouts }, { fieldLayout, fieldActions, fieldTimeouts }, { field2Layout, field2Actions, fieldTimeouts }, { gpsLayout, gpsActions, fieldTimeouts } }; +DispInfo *layouts = staticLayouts; + char Display::buf[17]; Display::Display() { - setLayout(1); + setLayout(0); +} + +#define MAXSCREENS 10 +#define DISP_ACTIONS_N 12 +#define DISP_TIMEOUTS_N 3 + +void Display::freeLayouts() { + if(layouts==staticLayouts) return; + DispInfo *old = layouts; + layouts=staticLayouts; + setLayout(0); + for(int i=0; ide = (DispEntry *)mem; + mem += (entries+1) * sizeof(DispEntry); + + d->actions = (uint8_t *)mem; + mem += DISP_ACTIONS_N * sizeof(uint8_t); + d->actions[0] = ACT_NONE; + + d->timeouts = (int16_t *)mem; + Serial.printf("allocated %d bytes (%d entries) for %p (addr=%p)\n", totalsize, entries, d, d->de); + return 0; +} + +void Display::parseDispElement(char *text, DispEntry *de) +{ + char type = *text; + if(type>='A'&&type<='Z') { + type += 32; // lc + de->fmt = FONT_LARGE; + } else { + de->fmt = FONT_SMALL; + } + switch(type) { + case 'l': + de->func = disp.drawLat; break; + case 'o': + de->func = disp.drawLon; break; + case 'a': + de->func = disp.drawAlt; break; + case 'h': + de->func = disp.drawHS; break; + case 'v': + de->func = disp.drawVS; break; + case 'i': + de->func = disp.drawID; + de->extra = strdup(text+1); + break; + case 'q': + de->func = disp.drawQS; break; + case 't': + de->func = disp.drawType; break; + case 'c': + de->func = disp.drawAFC; break; + case 'f': + de->func = disp.drawFreq; + de->extra = strdup(text+1); + Serial.printf("parsing 'f' entry: extra is '%s'\n", de->extra); + break; + case 'n': + de->func = disp.drawIP; break; + case 's': + de->func = disp.drawSite; break; + case 'g': + de->func = disp.drawGPS; + de->extra = strdup(text+1); + Serial.printf("parsing 'g' entry: extra is '%s'\n", de->extra); + break; + case 'r': + de->func = disp.drawRSSI; break; + case 'x': + de->func = disp.drawText; + de->extra = strdup(text+1); + break; + default: + Serial.printf("Unknown element: %c\n", type); + break; + } +} + +static uint8_t ACTION(char c) { + switch(c) { + case 'D': + return ACT_DISPLAY_DEFAULT; + case 'F': + return ACT_DISPLAY_SPECTRUM; + case 'W': + return ACT_DISPLAY_WIFI; + case '+': + return ACT_NEXTSONDE; + case '#': + return ACT_NONE; + default: + if(c>='0'&&c<='9') + return ACT_DISPLAY(c-'0'); + } + return ACT_NONE; +} + +void Display::initFromFile() { + File d = SPIFFS.open("/screens.txt", "r"); + if(!d) return; + + freeLayouts(); + layouts = (DispInfo *)malloc(MAXSCREENS * sizeof(DispInfo)); + if(!layouts) { + layouts = staticLayouts; + return; + } + memset(layouts, 0, MAXSCREENS * sizeof(DispInfo)); + + int idx = -1; + int what = -1; + int entrysize; + Serial.printf("Reading from /screens.txt. available=%d\n",d.available()); + while(d.available()) { + String line = d.readStringUntil('\n'); + line.trim(); + const char *s = line.c_str(); + Serial.printf("Line: '%s'\n", s); + if(*s == '#') continue; // ignore comments + switch(what) { + case -1: // wait for start of screen (@) + { + if(*s != '@') { + Serial.printf("Illegal start of screen: %s\n", s); + continue; + } + char *num = strchr(s, ':'); + if(!num) { + Serial.println("Line missing size length indication"); + continue; + } + entrysize = atoi(num+1); + Serial.printf("Reading entry with %d elements\n", entrysize); + idx++; + int res = allocDispInfo(entrysize, &layouts[idx]); + if(res<0) { + Serial.println("Error allocating memory for disp info"); + continue; + } + what = 0; + } + break; + default: // parse content... (additional data or line `what`) + if(strncmp(s,"timer=",6)==0) { // timer values + sscanf(s+6, "%hd,%hd,%hd", layouts[idx].timeouts, layouts[idx].timeouts+1, layouts[idx].timeouts+2); + Serial.printf("timer values: %d, %d, %d\n", layouts[idx].timeouts[0], layouts[idx].timeouts[1], layouts[idx].timeouts[2]); + } else if(strncmp(s, "key1action=",11)==0) { // key 1 actions + char c1,c2,c3,c4; + sscanf(s+11, "%c,%c,%c,%c", &c1, &c2, &c3, &c4); + layouts[idx].actions[1] = ACTION(c1); + layouts[idx].actions[2] = ACTION(c2); + layouts[idx].actions[3] = ACTION(c3); + layouts[idx].actions[4] = ACTION(c4); + } else if(strncmp(s, "key2action=",11)==0) { // key 2 actions + char c1,c2,c3,c4; + sscanf(s+11, "%c,%c,%c,%c", &c1, &c2, &c3, &c4); + layouts[idx].actions[5] = ACTION(c1); + layouts[idx].actions[6] = ACTION(c2); + layouts[idx].actions[7] = ACTION(c3); + layouts[idx].actions[8] = ACTION(c4); + Serial.printf("parsing key2action: %c %c %c %c\n", c1, c2, c3, c4); + } else if(strncmp(s, "timeaction=",11)==0) { // timer actions + char c1,c2,c3; + sscanf(s+11, "%c,%c,%c", &c1, &c2, &c3); + layouts[idx].actions[9] = ACTION(c1); + layouts[idx].actions[10] = ACTION(c2); + layouts[idx].actions[11] = ACTION(c3); + } else if(strchr(s, '=')) { // one line with some data... + int x,y; + char text[30]; + sscanf(s, "%d,%d=%30[^\r\n]", &y, &x, text); + layouts[idx].de[what].x = x; + layouts[idx].de[what].y = y; + parseDispElement(text, layouts[idx].de+what); + what++; + layouts[idx].de[what].func = NULL; + } else { + for(int i=0; i<12; i++) { + Serial.printf("action %d: %d\n", i, (int)layouts[idx].actions[i]); + } + what=-1; + } + break; + } + } } void Display::setLayout(int layoutIdx) { @@ -213,7 +417,6 @@ void Display::drawVS(DispEntry *de) { snprintf(buf, 16, " %+2.1f", sonde.si()->vs); u8x8->drawString(de->x, de->y, buf+strlen(buf)-5); u8x8->drawTile(de->x+5,de->y,2,ms_tiles); - } void Display::drawID(DispEntry *de) { SETFONT((de->fmt&0x01)); @@ -221,7 +424,23 @@ void Display::drawID(DispEntry *de) { u8x8->drawString(de->x, de->y, "nnnnnnnn "); return; } - u8x8->drawString(de->x, de->y, sonde.si()->id); + // TODO: handle DFM6 IDs + + if(!de->extra || de->extra[0]=='s') { + // real serial number, as printed on sonde + u8x8->drawString(de->x, de->y, sonde.si()->id); + } else if (de->extra[0]=='a') { + // autorx sonde number ("DF9" and last 6 digits of real serial number + strcpy(buf, sonde.si()->id); + memcpy(buf, "DF9", 3); + u8x8->drawString(de->x, de->y, buf); + } else { + // dxlAPRS sonde number (DF6 (why??) and 5 last digits of serial number as hex number + uint32_t id = atoi(sonde.si()->id); + id = id&0xfffff; + snprintf(buf, 16, "DF6%05X", id); + u8x8->drawString(de->x, de->y, buf); + } } void Display::drawRSSI(DispEntry *de) { SETFONT(de->fmt); @@ -317,18 +536,38 @@ void Display::drawGPS(DispEntry *de) { { // distance // equirectangular approximation is good enough - float lat1 = nmea.getLatitude()*0.000001; - float lat2 = sonde.si()->lat; - float x = radians(nmea.getLongitude()*0.000001-sonde.si()->lon) * cos( radians((lat1+lat2)/2) ); - float y = radians(lat2-lat1); - float d = sqrt(x*x+y*y)*EARTH_RADIUS; - snprintf(buf, 16, "d=%.0fm ", d); - buf[7]=0; + if( (sonde.si()->validPos&0x03)!=0x03 ) { + snprintf(buf, 16, "no pos "); + if(de->extra && *de->extra=='5') buf[5]=0; + } else if(!nmea.isValid()) { + snprintf(buf, 16, "no gps "); + if(de->extra && *de->extra=='5') buf[5]=0; + } else { + float lat1 = nmea.getLatitude()*0.000001; + float lat2 = sonde.si()->lat; + float x = radians(nmea.getLongitude()*0.000001-sonde.si()->lon) * cos( radians((lat1+lat2)/2) ); + float y = radians(lat2-lat1); + float d = sqrt(x*x+y*y)*EARTH_RADIUS; + if(de->extra && *de->extra=='5') { // 5-character version: ****m / ***km / **e6m + if(d>999999) snprintf(buf, 16, "%de6m ", (int)(d/1000000)); + if(d>9999) snprintf(buf, 16, "%dkm ", (int)(d/1000)); + else snprintf(buf, 16, "%dm ", (int)d); + buf[5]=0; + } else { // 6-character version: *****m / ****km) + if(d>99999) snprintf(buf, 16, "%dkm ", (int)(d/1000)); + else snprintf(buf, 16, "%dm ", (int)d); + buf[6]=0; + } + } u8x8->drawString(de->x, de->y, buf); } break; case 'I': // dIrection + if( (!nmea.isValid()) || ((sonde.si()->validPos&0x03)!=0x03 ) ) { + u8x8->drawString(de->x, de->y, "---"); + break; + } { float lat1 = radians(nmea.getLatitude()*0.000001); float lat2 = radians(sonde.si()->lat); @@ -337,10 +576,13 @@ void Display::drawGPS(DispEntry *de) { float y = sin(lon2-lon1)*cos(lat2); float x = cos(lat1)*sin(lat2) - sin(lat1)*cos(lat2)*cos(lon2-lon1); float dir = atan2(y, x)/PI*180; + if(dir<0) dir+=360; Serial.printf("direction is %.2f\n", dir); - snprintf(buf, 16, "dir=%d ", (int)dir); - buf[8]=0; + snprintf(buf, 16, "%3d", (int)dir); + buf[3]=0; u8x8->drawString(de->x, de->y, buf); + if(de->extra[1]==(char)176) + u8x8->drawTile(de->x+3, de->y, 1, deg_tile); } break; case 'E': diff --git a/libraries/SondeLib/Display.h b/libraries/SondeLib/Display.h index f0a7f90..2aff2b1 100644 --- a/libraries/SondeLib/Display.h +++ b/libraries/SondeLib/Display.h @@ -23,7 +23,13 @@ struct DispInfo { class Display { private: + void freeLayouts(); + int allocDispInfo(int entries, DispInfo *d); + void parseDispElement(char *text, DispEntry *de); + public: + void initFromFile(); + void setLayout(DispInfo *layout); DispInfo *layout; diff --git a/libraries/SondeLib/RS41.cpp b/libraries/SondeLib/RS41.cpp index 2fb6c5d..3fdd6e4 100644 --- a/libraries/SondeLib/RS41.cpp +++ b/libraries/SondeLib/RS41.cpp @@ -363,7 +363,10 @@ static void posrs41(const byte b[], uint32_t b_len, uint32_t p) Serial.print(getcard16(b, b_len, p+18UL)&255UL); Serial.print("Sats"); sonde.si()->alt = heig; - sonde.si()->validPos = true; + if( 0==(int)(lat*10000) && 0==(int)(long0*10000) ) + sonde.si()->validPos = 0; + else + sonde.si()->validPos = 0x3f; } /* end posrs41() */ diff --git a/libraries/SondeLib/Sonde.cpp b/libraries/SondeLib/Sonde.cpp index 42a1c55..d981299 100644 --- a/libraries/SondeLib/Sonde.cpp +++ b/libraries/SondeLib/Sonde.cpp @@ -329,6 +329,7 @@ void Sonde::receive() { int event = getKeyPressEvent(); if (!event) event = timeoutEvent(si); int action = (event==EVT_NONE) ? ACT_NONE : disp.layout->actions[event]; + Serial.printf("event %x: action is %x\n", event, action); // If action is to move to a different sonde index, we do update things here, set activate // to force the sx1278 task to call sonde.setup(), and pass information about sonde to // main loop (display update...) @@ -369,8 +370,7 @@ rxloop: /// TODO: THis has caused an exception when swithcing back to spectrumm... Serial.printf("waitRXcomplete returning %04x (%s)\n", res, (res&0xff)<4?RXstr[res&0xff]:""); // currently used only by RS92 - // TODO: rxtask.currentSonde might not be the right thing (after sonde channel change) - switch(sondeList[/*rxtask.*/currentSonde].type) { + switch(sondeList[rxtask.receiveSonde].type) { case STYPE_RS41: rs41.waitRXcomplete(); break; @@ -430,12 +430,8 @@ uint8_t Sonde::updateState(uint8_t event) { n = config.display; } if(n>=0&&n<5) { + Serial.printf("Setting display mode %d\n", n); disp.setLayout(n); - // TODO: This is kind of a hack... - // ACT_NEXTSONDE will cause loopDecoder to call enterMode(ST_DECODER) - //return ACT_NEXTSONDE; - - // TODO::: we probably should clear the display?? -- YES sonde.clearDisplay(); return 0xFF; } diff --git a/libraries/SondeLib/rs92gps.cpp b/libraries/SondeLib/rs92gps.cpp index 7365329..0d6aea6 100644 --- a/libraries/SondeLib/rs92gps.cpp +++ b/libraries/SondeLib/rs92gps.cpp @@ -73,7 +73,6 @@ int option_verbose = 0, // ausfuehrliche Anzeige rawin = 0; double dop_limit = 9.9; double d_err = 10000; -//double fixalt2d = 480; // bei mir zu Hause :-) TODO: make it configurable int rollover = 0, err_gps = 0;