some web interface updates

This commit is contained in:
Hansi Reiser 2019-04-08 19:22:08 +02:00
parent 0d64f8d05d
commit 7b58febb45
9 changed files with 376 additions and 94 deletions

View File

@ -43,3 +43,20 @@ A SHORT press will switch to the next channel in channels.txt
A medium press will active scan the whole band (400..406 MHz) and display a
spectrum diagram (each line == 50 kHz)
## Setup
Download https://github.com/me-no-dev/ESPAsyncWebServer/archive/master.zip
and move to your Arduino IDE's libraries directory
Rename to (name without "-master")
Download https://github.com/me-no-dev/AsyncTCP/archive/master.zip
and move to your Arduino IDE's libraries directory
Rename to (name without "-master")
Install Arduino ESP32 file system uploader
https://randomnerdtutorials.com/install-esp32-filesystem-uploader-arduino-ide/
Download https://github.com/me-no-dev/arduino-esp32fs-plugin/releases/download/1.0/ESP32FS-1.0.zip
Move to your Arduino IDE's tools directory

View File

@ -48,58 +48,29 @@ String processor(const String& var){
return String();
}
void SetupAsyncServer() {
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", "Hello, world");
});
server.on("/index.html", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/index.html", String(), false, processor);
});
// Route to load style.css file
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/style.css", "text/css");
});
#define MAX_QRG 10
// Route to set GPIO to HIGH
server.on("/on", HTTP_GET, [](AsyncWebServerRequest *request){
digitalWrite(ledPin, HIGH);
request->send(SPIFFS, "/index.html", String(), false, processor);
});
// Route to set GPIO to LOW
server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request){
digitalWrite(ledPin, LOW);
request->send(SPIFFS, "/index.html", String(), false, processor);
});
// Start server
server.begin();
const String sondeTypeSelect(int activeType) {
String sts = "";
for(int i=0; i<3; i++) {
sts += "<option value=\"";
sts += sondeTypeStr[i];
sts += "\"";
if(activeType==i) { sts += " selected"; }
sts += ">";
sts += sondeTypeStr[i];
sts += "</option>";
}
return sts;
}
int nNetworks;
struct { String id; String pw; } networks[20];
void setupWifiList() {
File file = SPIFFS.open("/networks.txt", "r");
if(!file){
Serial.println("There was an error opening the file '/networks.txt' for reading");
return;
}
int i=0;
while(file.available()) {
String line = file.readStringUntil('\n');
if(!file.available()) break;
networks[i].id = line;
networks[i].pw = file.readStringUntil('\n');
i++;
}
nNetworks = i;
Serial.print(i); Serial.println(" networks in networks.txt\n");
for(int j=0; j<i; j++) { Serial.print(networks[j].id); Serial.print(": "); Serial.println(networks[j].pw); }
}
//trying to work around
//"assertion "heap != NULL && "free() target pointer is outside heap areas"" failed:"
// which happens if request->send is called in createQRGForm!?!??
char message[10240];
///////////////////////// Functions for Reading / Writing QRG list from/to qrg.txt
void setupChannelList() {
File file = SPIFFS.open("/qrg.txt", "r");
@ -122,16 +93,211 @@ void setupChannelList() {
else if (space[1]=='9') { type=STYPE_DFM09; }
else if (space[1]=='6') { type=STYPE_DFM06; }
else continue;
Serial.printf("Adding %f with type %d\b",freq,type);
sonde.addSonde(freq, type);
int active = space[3]=='+'?1:0;
Serial.printf("Adding %f with type %d (active: %d)\n",freq,type,active);
sonde.addSonde(freq, type, active);
i++;
}
}
const char *createQRGForm() {
char *ptr = message;
strcpy(ptr,"<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"></head><body><form action=\"qrg.html\" method=\"post\"><table><tr><th>ID</th><th>Active</th><th>Freq</th><th>Mode</th></tr>");
for(int i=0; i<10; i++) {
String s = sondeTypeSelect(i>=sonde.nSonde?2:sonde.sondeList[i].type);
sprintf(ptr+strlen(ptr), "<tr><td>%d</td><td><input name=\"A%d\" type=\"checkbox\" %s/></td>"
"<td><input name=\"F%d\" type=\"text\" value=\"%3.3f\"></td>"
"<td><select name=\"T%d\">%s</select></td>",
i+1,
i+1, (i<sonde.nSonde&&sonde.sondeList[i].active)?"checked":"",
i+1, i>=sonde.nSonde?400.000:sonde.sondeList[i].freq,
i+1, s.c_str());
}
strcat(ptr,"</table><input type=\"submit\" value=\"Update\"/></form></body></html>");
return message;
}
const char *handleQRGPost(AsyncWebServerRequest *request) {
char label[10];
// parameters: a_i, f_1, t_i (active/frequency/type)
#if 1
File f = SPIFFS.open("/qrg.txt", "w");
if(!f) {
Serial.println("Error while opening '/qrg.txt' for writing");
return "Error while opening '/qrg.txt' for writing";
}
#endif
Serial.println("Handling post request");
#if 0
int params = request->params();
for(int i=0; i<params; i++) {
Serial.println(request->getParam(i)->name().c_str());
}
#endif
for(int i=1; i<=MAX_QRG; i++) {
snprintf(label, 10, "A%d", i);
AsyncWebParameter *active = request->getParam(label, true);
snprintf(label, 10, "F%d", i);
AsyncWebParameter *freq = request->getParam(label, true);
if(!freq) continue;
snprintf(label, 10, "T%d", i);
AsyncWebParameter *type = request->getParam(label, true);
if(!type) continue;
const char *fstr = freq->value().c_str();
const char *tstr = type->value().c_str();
Serial.printf("Processing a=%s, f=%s, t=%s\n", active?"YES":"NO", fstr, tstr);
char typech = (tstr[2]=='4'?'4':tstr[3]); // Ugly TODO
f.printf("%3.3f %c %c\n", atof(fstr), typech, active?'+':'-');
}
f.close();
setupChannelList();
}
/////////////////// Functions for reading/writing Wifi networks from networks.txt
#define MAX_WIFI 10
int nNetworks;
struct { String id; String pw; } networks[MAX_WIFI];
// FIXME: For now, we don't uspport wifi networks that contain newline or null characters
// ... would require a more sophisicated file format (currently one line SSID; one line Password
void setupWifiList() {
File file = SPIFFS.open("/networks.txt", "r");
if(!file){
Serial.println("There was an error opening the file '/networks.txt' for reading");
return;
}
int i=0;
while(file.available()) {
String line = file.readStringUntil('\n');
if(!file.available()) break;
networks[i].id = line;
networks[i].pw = file.readStringUntil('\n');
i++;
}
nNetworks = i;
Serial.print(i); Serial.println(" networks in networks.txt\n");
for(int j=0; j<i; j++) { Serial.print(networks[j].id); Serial.print(": "); Serial.println(networks[j].pw); }
}
const char *createWIFIForm() {
char *ptr = message;
char tmp[4];
strcpy(ptr,"<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"></head><body><form action=\"wifi.html\" method=\"post\"><table><tr><th>Nr</th><th>SSID</th><th>Password</th></tr>");
for(int i=0; i<MAX_WIFI; i++) {
sprintf(tmp,"%d",i);
sprintf(ptr+strlen(ptr), "<tr><td>%s</td><td><input name=\"S%d\" type=\"text\" value=\"%s\"/></td>"
"<td><input name=\"P%d\" type=\"text\" value=\"%s\"/></td>",
i==0?"<b>AP</b>":tmp,
i+1, i<nNetworks?networks[i].id.c_str():"",
i+1, i<nNetworks?networks[i].pw.c_str():"");
}
strcat(ptr,"</table><input type=\"submit\" value=\"Update\"></input></form></body></html>");
return message;
}
const char *handleWIFIPost(AsyncWebServerRequest *request) {
char label[10];
// parameters: a_i, f_1, t_i (active/frequency/type)
#if 1
File f = SPIFFS.open("/networks.txt", "w");
if(!f) {
Serial.println("Error while opening '/networks.txt' for writing");
return "Error while opening '/networks.txt' for writing";
}
#endif
Serial.println("Handling post request");
#if 0
int params = request->params();
for(int i=0; i<params; i++) {
Serial.println(request->getParam(i)->name().c_str());
}
#endif
for(int i=1; i<=MAX_WIFI; i++) {
snprintf(label, 10, "S%d", i);
AsyncWebParameter *ssid = request->getParam(label, true);
if(!ssid) continue;
snprintf(label, 10, "P%d", i);
AsyncWebParameter *pw = request->getParam(label, true);
if(!pw) continue;
const char *sstr = ssid->value().c_str();
const char *pstr = pw->value().c_str();
if(strlen(sstr)==0) continue;
Serial.printf("Processing S=%s, P=%s\n", sstr, pstr);
f.printf("%s\n%s\n", sstr, pstr);
}
f.close();
setupWifiList();
}
const char* PARAM_MESSAGE = "message";
void SetupAsyncServer() {
// Route for root / web page
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plain", "Hello, world");
});
server.on("/index.html", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/index.html", String(), false, processor);
});
server.on("/test.html", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/test.html", String(), false, processor);
});
server.on("/qrg.html", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/html", createQRGForm());
});
server.on("/qrg.html", HTTP_POST, [](AsyncWebServerRequest *request){
handleQRGPost(request);
request->send(200, "text/html", createQRGForm());
});
server.on("/wifi.html", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/html", createWIFIForm());
});
server.on("/wifi.html", HTTP_POST, [](AsyncWebServerRequest *request){
handleWIFIPost(request);
request->send(200, "text/html", createWIFIForm());
});
// Route to load style.css file
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/style.css", "text/css");
});
// Route to set GPIO to HIGH
server.on("/on", HTTP_GET, [](AsyncWebServerRequest *request){
digitalWrite(ledPin, HIGH);
request->send(SPIFFS, "/index.html", String(), false, processor);
});
// Route to set GPIO to HIGH
server.on("/test.php", HTTP_POST, [](AsyncWebServerRequest *request){
//digitalWrite(ledPin, HIGH);
request->send(SPIFFS, "/index.html", String(), false, processor);
});
// Route to set GPIO to LOW
server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request){
digitalWrite(ledPin, LOW);
request->send(SPIFFS, "/index.html", String(), false, processor);
});
// Send a POST request to <IP>/post with a form field message set to <message>
server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){
handleQRGPost(request);
request->send(200, "text/plain", "Hello, POST done");
});
// Start server
server.begin();
}
const char *fetchWifiPw(const char *id) {
for(int i=0; i<nNetworks; i++) {
Serial.print("Comparing '");
@ -369,7 +535,7 @@ void loopWifiScan() {
pw=fetchWifiPw(id);
if(pw) break;
}
if(1||!pw) { id="test"; pw="test"; }
if(!pw) { id="test"; pw="test"; }
Serial.print("Connecting to: "); Serial.println(id);
u8x8.drawString(0,6, "Conn:");
u8x8.drawString(6,6, id);
@ -386,11 +552,13 @@ void loopWifiScan() {
if(cnt==10) {
WiFi.disconnect();
delay(1000);
WiFi.softAP("sonde","sondesonde");
WiFi.softAP(networks[0].id.c_str(),networks[0].pw.c_str());
IPAddress myIP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(myIP);
sonde.setIP(myIP.toString().c_str());
u8x8.drawString(0,6, "AP: ");
u8x8.drawString(6,6, networks[0].id.c_str());
sonde.setIP(myIP.toString().c_str(), true);
sonde.updateDisplayIP();
SetupAsyncServer();
delay(5000);
@ -403,7 +571,7 @@ void loopWifiScan() {
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
sonde.setIP(WiFi.localIP().toString().c_str());
sonde.setIP(WiFi.localIP().toString().c_str(), false);
sonde.updateDisplayIP();
SetupAsyncServer();
delay(5000);

View File

@ -13,22 +13,44 @@
<p><a href="/on"><button class="button">ON</button></a></p>
<p><a href="/off"><button class="button button2">OFF</button></a></p>
-->
<table class="KKK">
<tr>
<th></th>
<th style="width:100px">ID</th>
<th style="width:100px">PW</th>
</tr>
<tr>
<td>1</td>
<td contenteditable>DinoGast</td>
<td contenteditable>Schokolade</td>
<tr>
<td>2</td>
<td contenteditable></td>
<td contenteditable></td>
</tr>
<div class="tab">
<button class="tablinks" onclick="selTab(event,'QRG')" id="defaultTab">QRG</button>
<button class="tablinks" onclick="selTab(event,'WIFI')">WLAN</button>
<button class="tablinks" onclick="selTab(event,'About')">About</button>
</div>
<div id="QRG" class="tabcontent">
<h3> QRG</h3>
<iframe src="qrg.html" style="border:none;" width="100%%" height="100%%"></iframe>
</div>
<div id="WIFI" class="tabcontent">
<h3> WIFI</h3>
<iframe src="wifi.html" style="border:none;" width="100%%" height="100%%"></iframe>
</div>
<div id="About" class="tabcontent">
<h3>About</h3>
RDZSonde
</div>
<script>
function selTab(evt, id) {
var i, tabcontent, tablinks;
tabcontent=document.getElementsByClassName("tabcontent");
for(i=0; i<tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
tablinks=document.getElementsByClassName("tablinks");
for(i=0; i<tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
document.getElementById(id).style.display = "block";
evt.currentTarget.className += " active";
}
document.getElementById("defaultTab").click();
</script>
</body>
</html>

View File

@ -1,3 +1,5 @@
RDZsonde
RDZsonde
DinoGast
Schokolade
AndroidDD

View File

@ -1,8 +1,8 @@
# Frequency in Mhz (format nnn.nnn)
# Type (4=RS41, 6=DFM normal, DFM-06, 9=DFM inverted, DFM-09)
#
402.700 4
402.300 4
403.450 9
405.100 4
402.700 4 +
402.300 4 +
403.450 9 +
405.100 4 -
# end

View File

@ -1,3 +1,40 @@
body, html {
height: 100%;
margin: 0;
font-family: Arial;
}
.tab {
overflow: hidden;
border: 1px solid #ccc;
}
.tab button {
background-color: inherit;
float: left;
border: none;
outline: none;
cursor: pointer;
padding: 14px 16px;
transition: 0.3s;
}
.tab button:hover {
background-color: #ddd;
}
.tab button.active {
background-color: #ccc;
}
.tabcontent {
display: none;
padding: 6px 12px;
border: 1px solid #ccc;
border-top: none;
height: 100%;
}
html {
font-family: Helvetica;
display: inline-block;

View File

@ -4,15 +4,18 @@
extern U8X8_SSD1306_128X64_NONAME_SW_I2C u8x8;
#define CHANBW 25
#define SMOOTH 2
#define CHANBW 10
#define PIXSAMPL (50/CHANBW)
#define SMOOTH 4
#define STARTF 400000000
#define NCHAN ((int)(6000/CHANBW))
int scanresult[NCHAN];
int scandisp[NCHAN/2];
int scandisp[NCHAN/PIXSAMPL];
#define PLOT_N 120
#define TICK1 (120/6)
#define TICK2 (TICK1/4)
#define PLOT_MIN -220
#define PLOT_SCALE(x) (x<PLOT_MIN?0:(x-PLOT_MIN)/2)
@ -37,6 +40,8 @@ void Scanner::plotResult()
for(int i=0; i<PLOT_N; i+=8) {
for(int j=0; j<8; j++) {
fillTiles(row+j, PLOT_SCALE(scandisp[i+j]));
if( ((i+j)%TICK1)==0) { row[j] |= 0x07; }
if( ((i+j)%TICK2)==0) { row[j] |= 0x01; }
}
for(int y=0; y<8; y++) {
u8x8.drawTile(i/8, y, 1, row+8*y);
@ -59,11 +64,12 @@ void Scanner::scan()
sx1278.writeRegister(REG_RSSI_CONFIG, SMOOTH&0x07);
sx1278.setFrequency(STARTF);
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
delay(5);
delay(20);
unsigned long start = millis();
uint32_t lastfrf=-1;
for(int i=0; i<NCHAN; i++) {
for(int iter=0; iter<2; iter++) { // two interations, to catch all RS41 transmissions
for(int i=0; i<NCHAN; i++) {
float freq = STARTF + 1000.0*i*CHANBW;
uint32_t frf = freq * 1.0 * (1<<19) / SX127X_CRYSTAL_FREQ;
if( (lastfrf>>16)!=(frf>>16) ) {
@ -78,16 +84,24 @@ void Scanner::scan()
int wait = 20 + 1000*(1<<(SMOOTH+1))/4/CHANBW;
delayMicroseconds(wait);
int rssi = -(int)sx1278.readRegister(REG_RSSI_VALUE_FSK);
scanresult[i] = rssi;
if(iter==0) { scanresult[i] = rssi; } else {
if(rssi>scanresult[i]) scanresult[i]=rssi;
}
}
}
unsigned long duration = millis()-start;
Serial.print("Scan time: ");
Serial.println(duration);
for(int i=0; i<NCHAN; i+=2) {
scandisp[i/2] = scanresult[i];
for(int j=1; j<2; j++) { if(scanresult[i+j]>scandisp[i/2]) scandisp[i/2] = scanresult[i+j]; }
for(int i=0; i<NCHAN; i+=PIXSAMPL) {
scandisp[i/PIXSAMPL]=scanresult[i];
for(int j=1; j<PIXSAMPL; j++) { scandisp[i/PIXSAMPL]+=scanresult[i+j]; }
//for(int j=1; j<PIXSAMPL; j++) { if(scanresult[i+j]>scandisp[i/PIXSAMPL]) scandisp[i/PIXSAMPL] = scanresult[i+j]; }
Serial.print(scanresult[i]); Serial.print(", ");
if(((i+1)%32) == 0) Serial.println();
}
Serial.println("\n");
for(int i=0; i<NCHAN/PIXSAMPL; i++) {
scandisp[i]/=PIXSAMPL;
Serial.print(scandisp[i]); Serial.print(", ");
}
Serial.println("\n");
}

View File

@ -22,7 +22,7 @@ static unsigned char stattiles[4][4] = {
0x1F, 0x15, 0x15, 0x00 ,
0x00, 0x1F, 0x00, 0x00 };
byte myIP_tiles[8*10];
byte myIP_tiles[8*11];
static const uint8_t font[10][5]={
0x3E, 0x51, 0x49, 0x45, 0x3E, // 0
@ -37,12 +37,23 @@ static const uint8_t font[10][5]={
0x06, 0x49, 0x39, 0x29, 0x1E }; // 9; .=0x40
static uint8_t halfdb_tile[8]={0x80, 0x27, 0x45, 0x45, 0x45, 0x39, 0x00, 0x00};
static uint8_t halfdb_tile1[8]={0x00, 0x38, 0x28, 0x28, 0x28, 0xC8, 0x00, 0x00};
static uint8_t halfdb_tile2[8]={0x00, 0x11, 0x02, 0x02, 0x02, 0x01, 0x00, 0x00};
static uint8_t empty_tile[8]={0x80, 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, 0x00};
static uint8_t empty_tile1[8]={0x00, 0xF0, 0x88, 0x48, 0x28, 0xF0, 0x00, 0x00};
static uint8_t empty_tile2[8]={0x00, 0x11, 0x02, 0x02, 0x02, 0x01, 0x00, 0x00};
static uint8_t ap_tile[8]={0x00,0x04,0x22,0x92, 0x92, 0x22, 0x04, 0x00};
void Sonde::setIP(const char *ip) {
int tp = 0;
void Sonde::setIP(const char *ip, bool AP) {
memset(myIP_tiles, 0, 11*8);
int len = strlen(ip);
int pix = (len-3)*6+6;
int tp = 80-pix+8;
if(AP) memcpy(myIP_tiles+(tp<16?0:8), ap_tile, 8);
for(int i=0; i<len; i++) {
if(ip[i]=='.') { myIP_tiles[tp++]=0x40; myIP_tiles[tp++]=0x00; }
else {
@ -58,18 +69,26 @@ void Sonde::setIP(const char *ip) {
void Sonde::clearSonde() {
nSonde = 0;
}
void Sonde::addSonde(float frequency, SondeType type) {
void Sonde::addSonde(float frequency, SondeType type, int active) {
if(nSonde>=MAXSONDE) {
Serial.println("Cannot add another sonde, MAXSONDE reached");
return;
}
sondeList[nSonde].type = type;
sondeList[nSonde].freq = frequency;
sondeList[nSonde].active = active;
memcpy(sondeList[nSonde].rxStat, "\x00\x01\x2\x3\x2\x1\x1\x2\x0\x3\x0\x0\x1\x2\x3\x1\x0", 18);
nSonde++;
}
void Sonde::nextConfig() {
currentSonde++;
// Skip non-active entries (but don't loop forever if there are no active ones
for(int i=0; i<MAXSONDE; i++) {
if(!sondeList[currentSonde].active) {
currentSonde++;
if(currentSonde>=nSonde) currentSonde=0;
}
}
if(currentSonde>=nSonde) {
currentSonde=0;
}
@ -160,7 +179,8 @@ void Sonde::updateDisplayRSSI() {
int len=strlen(buf)-3;
buf[5]=0;
u8x8.drawString(0,6,buf);
u8x8.drawTile(len,6,1,(sonde.si()->rssi&1)?halfdb_tile:empty_tile);
u8x8.drawTile(len,6,1,(sonde.si()->rssi&1)?halfdb_tile1:empty_tile1);
u8x8.drawTile(len,7,1,(sonde.si()->rssi&1)?halfdb_tile2:empty_tile2);
}
void Sonde::updateStat() {
@ -183,7 +203,7 @@ void Sonde::updateDisplayRXConfig() {
}
void Sonde::updateDisplayIP() {
u8x8.drawTile(6, 7, 10, myIP_tiles);
u8x8.drawTile(5, 7, 11, myIP_tiles);
}
// Probing RS41

View File

@ -12,6 +12,7 @@ extern const char *sondeTypeStr[5];
typedef struct st_sondeinfo {
// receiver configuration
bool active;
SondeType type;
float freq;
// decoded ID
@ -37,12 +38,13 @@ typedef struct st_sondeinfo {
class Sonde
{
private:
int nSonde;
int currentSonde = 0;
SondeInfo sondeList[MAXSONDE+1];
public:
int nSonde;
SondeInfo sondeList[MAXSONDE+1];
void clearSonde();
void addSonde(float frequency, SondeType type);
void addSonde(float frequency, SondeType type, int active);
void nextConfig();
void setup();
@ -59,7 +61,7 @@ public:
void updateDisplay();
void updateDisplayScanner();
void clearDisplay();
void setIP(const char *ip);
void setIP(const char *ip, bool isAP);
};
extern Sonde sonde;