#include #include #include #include #include #include #include "hal.h" // #include "tcpip_adapter.h" // #include "esp_wifi.h" // #include "esp_event_loop.h" #include "esp_http_server.h" #include "format.h" // #include "fifo.h" // #include "socket.h" #include "proc.h" #include "gps.h" #include "log.h" #include "http.h" // #define DEBUG_PRINT #ifdef WITH_HTTP // ============================================================================================================ static void SelectList(httpd_req_t *Req, const char *Name, const char **List, int Size, int Sel=0) { char Line[8]; httpd_resp_sendstr_chunk(Req, "\n"); } static void ParmForm_Info(httpd_req_t *Req) // produce HTML form for aircraft parameters { httpd_resp_sendstr_chunk(Req, "
\n\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "
Info
Pilot
Crew
Base airfield
Registration
Manufacturer
Model
Type
\n"); } static void ParmForm_Acft(httpd_req_t *Req) // produce HTML form for aircraft parameters { char Line[16]; httpd_resp_sendstr_chunk(Req, "
\n\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); const char *AddrTypeTable[4] = { "Random", "ICAO", "FLARM", "OGN" } ; httpd_resp_sendstr_chunk(Req, "\n"); const char *AcftTypeTable[16] = { "Unknown", "(moto)Glider", "Tow-plane", "Helicopter", "Parachute", "Drop-plane", "Hang-glider", "Para-glider", "Powered-aircraft", "Jet-aircraft", "UFO", "Balloon", "Airship", "UAV/drone", "Ground support", "Static object" } ; httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "
Aircraft
Address>16)); Format_Hex(Line+2, (uint16_t)Parameters.Address); httpd_resp_send_chunk(Req, Line, 6); httpd_resp_sendstr_chunk(Req, "\">
Addr-Type\n"); SelectList(Req, "AddrType", AddrTypeTable, 4, Parameters.AddrType); httpd_resp_sendstr_chunk(Req, "
Acft-Type\n"); SelectList(Req, "AcftType", AcftTypeTable, 16, Parameters.AcftType); httpd_resp_sendstr_chunk(Req, "
\n"); } static void ParmForm_Other(httpd_req_t *Req) // produce HTML form for aircraft parameters { char Line[16]; int Len; httpd_resp_sendstr_chunk(Req, "
\n\n"); httpd_resp_sendstr_chunk(Req, "\n"); const char *FreqPlanTable[16] = { "Auto", "Europe/Africa", "USA/Canada", "Australia/Chile", "New Zeeland", "Izrael" }; httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "
Other
Freq. plan\n"); SelectList(Req, "FreqPlan", FreqPlanTable, 6, Parameters.FreqPlan); httpd_resp_sendstr_chunk(Req, "
Tx power [dBm]
Freq.corr. [ppm]
Console baud
Verbose
Page sel. mask
\n"); } #ifdef WITH_AP static void ParmForm_AP(httpd_req_t *Req) // Wi-Fi access point parameters { char Line[16]; int Len; { char Line[16]; int Len; httpd_resp_sendstr_chunk(Req, "
\n\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "
Wi-Fi
SSID
Password
Data port
Tx [dBm]>2, 2, 1); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, "\">
\n"); } #endif // ============================================================================================================ static void Table_GPS(httpd_req_t *Req) { char Line[32]; int Len; uint32_t Time=TimeSync_Time(); uint32_t Sec = (Time-1)%60; GPS_Position *GPS = GPS_getPosition(Sec); if(GPS==0) return; httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); if(GPS->hasBaro) { httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); } httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "
GPS"); Len=Format_UnsDec(Line, GPS->Year+2000 , 4); Line[Len++]='.'; Len+=Format_UnsDec(Line+Len, GPS->Month, 2); Line[Len++]='.'; Len+=Format_UnsDec(Line+Len, GPS->Day , 2); Line[Len++]=' '; Len+=Format_UnsDec(Line+Len, GPS->Hour , 2); Line[Len++]=':'; Len+=Format_UnsDec(Line+Len, GPS->Min , 2); Line[Len++]=':'; Len+=Format_UnsDec(Line+Len, GPS->Sec , 2); Line[Len++]='.'; Len+=Format_UnsDec(Line+Len, GPS->FracSec, 2); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, "
Lock"); if(GPS->FixMode>=2) { strcpy(Line, "0-D "); Line[0]='0'+GPS->FixMode; Len=4; } else { strcpy(Line, "--- "); Len=4; } Len+=Format_UnsDec(Line+Len, GPS->Satellites); Len+=Format_String(Line+Len, "sat Hdop"); Len+=Format_UnsDec(Line+Len, GPS->HDOP, 2, 1); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, "
Latitude"); Len=Format_SignDec(Line, GPS->Latitude/6, 7, 5); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, "°
Longitude"); Len=Format_SignDec(Line, GPS->Longitude/6, 8, 5); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, "°
Altitude"); Len=Format_SignDec(Line, GPS->Altitude, 2, 1); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, " m
Pressure"); Len=Format_SignDec(Line, (GPS->Pressure+2)/4, 3, 2); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, " hPa
Pressure Alt."); Len=Format_SignDec(Line, GPS->StdAltitude, 2, 1); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, " m
Temperature"); Len=Format_SignDec(Line, GPS->Temperature, 2, 1); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, " ℃
Climb rate"); Len=Format_SignDec(Line, GPS->ClimbRate, 2, 1); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, " m/s
Hor. speed"); Len=Format_UnsDec(Line, GPS->Speed, 2, 1); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, " m/s
Hor. track"); Len=Format_UnsDec(Line, GPS->Heading, 4, 1); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, "°
\n"); } static void Table_RF(httpd_req_t *Req) { char Line[128]; int Len; httpd_resp_sendstr_chunk(Req, "\n"); Len=Format_String(Line, "\n"); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, "
RF chip"); #ifdef WITH_RFM69 Len+=Format_String(Line+Len, "sx1276"); #endif #ifdef WITH_RFM95 Len+=Format_String(Line+Len, "RFM95"); #endif Len+=Format_String(Line+Len, "
\n"); } static uint8_t BattCapacity(uint16_t mVolt) { if(mVolt>=4100) return 100; if(mVolt<=3600) return 0; return (mVolt-3600+2)/5; } static void Table_Batt(httpd_req_t *Req) { char Line[16]; int Len; httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); #ifdef WITH_BQ uint8_t Status = BQ.readStatus(); // read status register uint8_t State = (Status>>4)&0x03; // charging status const char *StateName[4] = { "Charge OFF" , "Pre-charge", "Charging", "Full" } ; httpd_resp_sendstr_chunk(Req, "\n"); #endif #ifdef WITH_AXP uint16_t InpCurr=AXP.readBatteryInpCurrent(); // [mA] uint16_t OutCurr=AXP.readBatteryOutCurrent(); // [mA] uint16_t Vbus=AXP.readVbusVoltage(); // [mV] uint16_t VbusCurr=AXP.readVbusCurrent(); // [mA] int16_t Temp=AXP.readTemperature(); // [0.1degC] uint32_t InpCharge=AXP.readBatteryInpCharge(); uint32_t OutCharge=AXP.readBatteryOutCharge(); int16_t Current = InpCurr-OutCurr; httpd_resp_sendstr_chunk(Req, "\n"); int32_t Charge = InpCharge-OutCharge; httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); #endif httpd_resp_sendstr_chunk(Req, "
Battery
Voltage"); #ifdef WITH_MAVLINK Len=Format_UnsDec(Line, MAVLINK_BattVolt, 4, 3); #else Len=Format_UnsDec(Line, BatteryVoltage>>8, 4, 3); // print the battery voltage readout #endif httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, " V
Capacity"); #ifdef WITH_MAVLINK uint8_t Cap=MAVLINK_BattCap; // [%] from the drone's telemetry #else uint8_t Cap=BattCapacity(BatteryVoltage>>8); // [%] est. battery capacity based on the voltage readout #endif Len=Format_UnsDec(Line, (uint16_t)Cap); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, " %
State"); httpd_resp_sendstr_chunk(Req, StateName[State]); httpd_resp_sendstr_chunk(Req, "
Current"); Len=Format_SignDec(Line, Current, 3); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, " mA
Charge"); Len=Format_UnsDec(Line, (((int64_t)Charge<<12)+562)/1125, 2, 1); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, " mAh
USB volt."); Len=Format_UnsDec(Line, Vbus, 4, 3); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, " V
USB curr."); Len=Format_UnsDec(Line, VbusCurr, 4, 3); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, " A
\n"); } static void Top_Bar(httpd_req_t *Req) { char Line[32]; int Len; httpd_resp_sendstr_chunk(Req, "

OGN-Tracker

\n"); httpd_resp_sendstr_chunk(Req, "CPU ID: "); Len=Format_Hex(Line, getUniqueID()); httpd_resp_send_chunk(Req, Line, Len); httpd_resp_sendstr_chunk(Req, "
\n"); httpd_resp_sendstr_chunk(Req, "\n\ \n\ \n\ \n\
StatusConfigurationLog files

\n"); } // ============================================================================================================ static esp_err_t parm_get_handler(httpd_req_t *Req) { // char Line[32]; int Len; uint16_t URLlen=httpd_req_get_url_query_len(Req); if(URLlen) { char *URL = (char *)malloc(URLlen+1); httpd_req_get_url_query_str(Req, URL, URLlen+1); #ifdef DEBUG_PRINT xSemaphoreTake(CONS_Mutex, portMAX_DELAY); Format_String(CONS_UART_Write, "parm_get_handler() => ["); Format_SignDec(CONS_UART_Write, URLlen); Format_String(CONS_UART_Write, "] "); Format_String(CONS_UART_Write, URL); Format_String(CONS_UART_Write, "\n"); xSemaphoreGive(CONS_Mutex); #endif char *Line=URL; for( ; ; ) { Parameters.ReadLine(Line); Line = strchr(Line, '&'); if(Line==0) break; Line++; } free(URL); Parameters.WriteToNVS(); } httpd_resp_sendstr_chunk(Req, "\ \n\ \n\ \n\ \n\ \n\ \n\ OGN-Tracker configuration\n\ "); Top_Bar(Req); httpd_resp_sendstr_chunk(Req, "\n\n\n\n\n\n
\n"); ParmForm_Acft(Req); httpd_resp_sendstr_chunk(Req, "
\n"); ParmForm_Info(Req); httpd_resp_sendstr_chunk(Req, "
\n"); #ifdef WITH_AP ParmForm_AP(Req); httpd_resp_sendstr_chunk(Req, "
\n"); #endif ParmForm_Other(Req); httpd_resp_sendstr_chunk(Req, "
\n"); httpd_resp_sendstr_chunk(Req, "
\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, "
\n"); httpd_resp_sendstr_chunk(Req, "
\n"); httpd_resp_sendstr_chunk(Req, "\n\n"); /* httpd_resp_sendstr_chunk(Req, "
\n"); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, ""); httpd_resp_sendstr_chunk(Req, "
\n"); */ // httpd_resp_sendstr_chunk(Req, 0); httpd_resp_send_chunk(Req, 0, 0); return ESP_OK; } static esp_err_t top_get_handler(httpd_req_t *Req) { httpd_resp_sendstr_chunk(Req, "\ \n\ \n\ OGN-Tracker status\n\ "); Top_Bar(Req); Table_GPS(Req); httpd_resp_sendstr_chunk(Req, "
\n"); Table_RF(Req); httpd_resp_sendstr_chunk(Req, "
\n"); Table_Batt(Req); httpd_resp_sendstr_chunk(Req, "\n"); httpd_resp_sendstr_chunk(Req, 0); return ESP_OK; } static int Format_DateTime(char *Out, time_t Time) { struct tm *TM = gmtime(&Time); int Len=Format_UnsDec(Out, (uint16_t)1900+TM->tm_year, 4); Out[Len++]='.'; Len+=Format_UnsDec(Out+Len, (uint16_t)1+TM->tm_mon, 2); Out[Len++]='.'; Len+=Format_UnsDec(Out+Len, (uint16_t)TM->tm_mday, 2); Out[Len++]=' '; Len+=Format_UnsDec(Out+Len, (uint16_t)TM->tm_hour, 2); Out[Len++]=':'; Len+=Format_UnsDec(Out+Len, (uint16_t)TM->tm_min, 2); Out[Len++]=':'; Len+=Format_UnsDec(Out+Len, (uint16_t)TM->tm_sec, 2); return Len; } static int LogFileName(char *Name, time_t Time, const char *Ext=0) { int Len=Parameters.getAprsCall(Name); Name[Len++]='_'; struct tm *TM = gmtime(&Time); Len+=Format_UnsDec(Name+Len, (uint16_t)1900+TM->tm_year, 4); Name[Len++]='.'; Len+=Format_UnsDec(Name+Len, (uint16_t)1+TM->tm_mon, 2); Name[Len++]='.'; Len+=Format_UnsDec(Name+Len, (uint16_t)TM->tm_mday, 2); Name[Len++]='_'; Len+=Format_UnsDec(Name+Len, (uint16_t)TM->tm_hour, 2); Len+=Format_UnsDec(Name+Len, (uint16_t)TM->tm_min, 2); Len+=Format_UnsDec(Name+Len, (uint16_t)TM->tm_sec, 2); if(Ext) Len+=Format_String(Name+Len, Ext); Name[Len]=0; return Len; } // send give log file in the APRS format static esp_err_t SendLog_APRS(httpd_req_t *Req, const char *FileName, uint32_t FileTime) { char ContDisp[64]; char Line[1000]; int Len=0; Len=Format_String(ContDisp, "attachement; filename=\""); Len+=LogFileName(ContDisp+Len, FileTime, ".aprs"); ContDisp[Len++]='\"'; ContDisp[Len]=0; httpd_resp_set_hdr(Req, "Content-Disposition", ContDisp); httpd_resp_set_type(Req, "text/plain"); FILE *File = fopen(FileName, "rb"); if(File==0) { httpd_resp_send_chunk(Req, 0, 0); return ESP_OK; } OGN_LogPacket Packet; for( ; ; ) { if(fread(&Packet, Packet.Bytes, 1, File)!=1) break; // read the next packet if(!Packet.isCorrect()) continue; uint32_t Time = Packet.getTime(FileTime); // [sec] get exact time from short time in the packet and the file start time Len+=Packet.Packet.WriteAPRS(Line+Len, Time); // print the packet in the APRS format if(Len>850) { httpd_resp_send_chunk(Req, Line, Len); Len=0; } vTaskDelay(1); } fclose(File); if(Len) { httpd_resp_send_chunk(Req, Line, Len); Len=0; } httpd_resp_send_chunk(Req, 0, 0); return ESP_OK; } // send given log file in the TLG (binary) format static esp_err_t SendLog_TLG(httpd_req_t *Req, const char *FileName, uint32_t FileTime) { char ContDisp[64]; char Line[512]; int Len; Len=Format_String(ContDisp, "attachement; filename=\""); Len+=Parameters.getAprsCall(ContDisp+Len); ContDisp[Len++]='_'; Len+=Format_Hex(ContDisp+Len, FileTime); Len+=Format_String(ContDisp+Len, ".TLG"); ContDisp[Len++]='\"'; ContDisp[Len]=0; httpd_resp_set_hdr(Req, "Content-Disposition", ContDisp); httpd_resp_set_type(Req, "application/octet-stream"); FILE *File = fopen(FileName, "rb"); if(File==0) { httpd_resp_send_chunk(Req, 0, 0); return ESP_OK; } for( ; ; ) { Len=fread(Line, 1, 512, File); if(Len<=0) break; httpd_resp_send_chunk(Req, Line, Len); vTaskDelay(1); } fclose(File); httpd_resp_send_chunk(Req, 0, 0); return ESP_OK; } // handle the HTTP request for the log files page static esp_err_t log_get_handler(httpd_req_t *Req) { char FullName[32]; char Line[256]; struct stat Stat; const char *Path= "/spiffs"; uint16_t URLlen=httpd_req_get_url_query_len(Req); if(URLlen) { char *URL = (char *)malloc(URLlen+1); httpd_req_get_url_query_str(Req, URL, URLlen+1); char Name[16]; bool SendFile = httpd_query_key_value(URL, "File", Name, 16)==ESP_OK; char Format[8] = { 0 }; httpd_query_key_value(URL, "Format", Format, 8); free(URL); if(SendFile) { AddPath(FullName, Name, Path); uint32_t Time=FlashLog_ReadShortFileTime(Name); if(Time) { if(strcmp(Format, "APRS")==0) return SendLog_APRS(Req, FullName, Time); return SendLog_TLG(Req, FullName, Time); } } } httpd_resp_sendstr_chunk(Req, "\ \n\ \n\ \n\ \n\ \n\ \n\ OGN-Tracker log files\n\ "); Top_Bar(Req); std::vector FileList; // list of log files DIR *Dir=opendir(Path); // open the log file directory if(Dir==0) { httpd_resp_sendstr_chunk(Req, "

Cannot open the log directory !

\n"); httpd_resp_send_chunk(Req, 0, 0); return ESP_OK; } for( ; ; ) { struct dirent *Ent = readdir(Dir); if(!Ent) break; // read next directory entry, break if all read if(Ent->d_type != DT_REG) continue; // skip non-regular files char *Name = Ent->d_name; uint32_t Time=FlashLog_ReadShortFileTime(Name); // read time from the file name if(Time==0) continue; // skip if not .TLG format FileList.push_back(Time); } closedir(Dir); std::sort(FileList.begin(), FileList.end()); httpd_resp_sendstr_chunk(Req, "\n\n"); for(size_t Idx=0; Idx=0) // get file info Size = Stat.st_size; int Len=Format_String(Line, "\n"); httpd_resp_send_chunk(Req, Line, Len); vTaskDelay(1); } httpd_resp_sendstr_chunk(Req, "
File[KB]Date
"); Len+=Format_String(Line+Len, Name); Len+=Format_String(Line+Len, "APRS"); Len+=Format_UnsDec(Line+Len, (Size+512)>>10); Len+=Format_String(Line+Len, ""); Len+=Format_DateTime(Line+Len, Time); Len+=Format_String(Line+Len, "
\n"); httpd_resp_sendstr_chunk(Req, "\n\n"); httpd_resp_send_chunk(Req, 0, 0); return ESP_OK; } static esp_err_t logo_get_handler(httpd_req_t *Req) { extern const uint8_t OGN_logo_jpg[] asm("_binary_OGN_logo_240x240_jpg_start"); extern const uint8_t OGN_logo_end[] asm("_binary_OGN_logo_240x240_jpg_end"); const int OGN_logo_size = OGN_logo_end-OGN_logo_jpg; httpd_resp_set_type(Req, "image/jpeg"); httpd_resp_send(Req, (const char *)OGN_logo_jpg, OGN_logo_size); return ESP_OK; } static const httpd_uri_t HTTPtop = { .uri = "/", .method = HTTP_GET, .handler = top_get_handler, .user_ctx = 0 }; static const httpd_uri_t HTTPlogo = { .uri = "/logo.jpeg", .method = HTTP_GET, .handler = logo_get_handler, .user_ctx = 0 }; static const httpd_uri_t HTTPparm = { .uri = "/parm.html", .method = HTTP_GET, .handler = parm_get_handler, .user_ctx = 0 }; static const httpd_uri_t HTTPlog = { .uri = "/log.html", .method = HTTP_GET, .handler = log_get_handler, .user_ctx = 0 }; static httpd_handle_t HTTPserver = 0; esp_err_t HTTP_Start(int MaxSockets, int Port) { httpd_config_t Config = HTTPD_DEFAULT_CONFIG(); Config.server_port = Port; Config.task_priority = tskIDLE_PRIORITY+3; Config.max_open_sockets = MaxSockets; esp_err_t Err=httpd_start(&HTTPserver, &Config); if(Err!=ESP_OK) return Err; httpd_register_uri_handler(HTTPserver, &HTTPtop); // top URL httpd_register_uri_handler(HTTPserver, &HTTPparm); // parameters URL httpd_register_uri_handler(HTTPserver, &HTTPlog); // log files URL httpd_register_uri_handler(HTTPserver, &HTTPlogo); // OGN logo return Err; } void HTTP_Stop(void) { if(HTTPserver) httpd_stop(HTTPserver); HTTPserver=0; } // ============================================================================================================ #endif // WITH_HTTP