/* * "simpleSA_wifi.cpp" * * Trial wifi charting functionality to see if WiFi affects the scan results * Based on example here https://circuits4you.com/2019/01/11/esp8266-data-logging-with-real-time-graphs/ * * Requires some files to be placed into the spiffs area of flash * * Modified in Version 2.1 by WA2FZW: * * Eliminated all calls to "WriteSettings". All of the functions in "Cmd.cpp" * that actually update the "setting" structure elements now handle that task * and do so based on whether or not the value of the parameter actually * changed or not which wasn't the case before. */ #include "simpleSA_wifi.h" // WiFi definitions #include "si4432.h" // Si4432 definitions #include "cmd.h" // Command processing functions /* * Variables to determine size of grid and waterfall * In Bandscope mode the grid is reduced. In future it may be possible to add * a waterfall to the main sweep, but not yet */ extern uint16_t gridHeight; extern uint16_t gridWidth; extern uint16_t yGrid; // no of grid divisions extern uint16_t yDelta; // no of points/division extern uint16_t xGrid; extern uint16_t displayPoints; extern uint16_t xDelta; extern uint16_t waterfallHeight; extern unsigned long sweepMicros; // To report the scan time extern uint16_t bpfCount; // Number of elements in the bandpassFilters array extern double bpfCalibrations[MAX_SI4432_FILTERS]; // temporary storage for calibration values extern uint16_t bpfIndex; // Index for current rbw filter extern int updateSidebar; // Flag to indicate no of clients has changed extern void ClearDisplay (); extern void DisplayError ( uint8_t severity, const char *l1, const char *l2, const char *l3, const char *l4 ); extern void setMode (uint16_t newMode); /* * In Version 1.8, the transmitter and receiver Si4432 modules are implemented as * objects. */ extern Si4432 rcvr; // Object for the receiver extern Si4432 xmit; // And the transmitter extern bool AGC_On; // Flag indicates if Preamp AGC is enabled extern uint8_t AGC_Reg; // Fixed value for preampGain if not auto extern uint32_t startFreq_IF; extern uint32_t stopFreq_IF; extern uint32_t sigFreq_IF; extern uint16_t sigGenOutputOn; extern uint32_t startFreq_RX; extern uint32_t stopFreq_RX; AsyncWebServer server ( 80 ); // Create the webserver object AsyncResponseStream *response; /* * Install WebSockets library by Markus Sattler * https://github.com/Links2004/arduinoWebSockets */ WebSocketsServer webSocket = WebSocketsServer ( 81 ); uint8_t socketNumber; unsigned long messageNumber; extern uint8_t numberOfWebsocketClients; /* * Tracking of number of Wi-Fi reconnects and total connection time */ unsigned long numberOfReconnects; unsigned long millisConnected; IPAddress ipAddress; // Store the IP address for use elsewhere, eg info /* * Function to format IP address nicely */ char *FormatIPAddress ( IPAddress ipAddress ) { static char formatBuffer[20] = {0}; sprintf( formatBuffer, "%d.%d.%d.%d", ipAddress[0], ipAddress[1], ipAddress[2], ipAddress[3] ); return formatBuffer; } boolean startAP () // Start the WiFi Access Point, keep the user informed. { ClearDisplay (); // Fade to black Serial.println ( "Starting Access Point" ); // Put in the instructions delay(2000); // Scan WiFi SSIDs WiFi.scanNetworks(true); /* * Start by kicking off the soft-AP. Call depends on whether or not password * is required to connect */ #ifdef AP_PASSWORD boolean result = WiFi.softAP ( PROGRAM_NAME, AP_PASSWORD ); #else boolean result = WiFi.softAP ( PROGRAM_NAME ); #endif if ( !result ) // This has failed, tell the user DisplayError ( ERR_WARN, "Failed to open AP:", "WiFi Disabled", NULL, NULL ); else ipAddress = WiFi.softAPIP(); Serial.printf ( "Access Point started, result = %i \n", result ); return result; } boolean connectWiFi() // Connect to Wifi using SSID (ie via Router) { Serial.printf ( "Connecting to: %s \n", WIFI_SSID ); // Serial.println( (char *) &configuration.WiFi_SSID[0] ); WiFi.softAPdisconnect (); // Disconnect anything that we may have /* * Start the connection: */ // WiFi.begin ( (char *) &configuration.WiFi_SSID[0], (char *) &configuration.WiFi_Password[0] ); WiFi.begin ( WIFI_SSID, WIFI_PASSWORD ); WiFi.setSleep (false ); // Disable sleep /* * Apply hostname */ WiFi.setHostname ( PROGRAM_NAME ); int maxTry = 10; // Wait for the connection to be made. tft.setCursor ( 0, 190 ); tft.printf ( "Connecting to WiFi %s", WIFI_SSID ); while (( WiFi.status() != WL_CONNECTED ) && ( maxTry > 0 )) { tft.print("."); // Nice touch!!! delay ( 1000 ); // Wait and update the try count. maxTry--; if ( maxTry <= 0 ) { DisplayError ( ERR_WARN, "Connecting to", WIFI_SSID, "failed!", "WiFi Disabled" ); return false; } } ipAddress = WiFi.localIP (); // We are connected, display the IP address Serial.printf ( "Connected - IP %s \n", FormatIPAddress ( ipAddress )); tft.printf ( "\n\nConnected - IP %s \n", FormatIPAddress ( ipAddress )); } /* * Handle websocket events * This is how dta is sent from the web page to the ESP32 */ void webSocketEvent ( uint8_t num, WStype_t type, uint8_t* payload, size_t payloadLength ) { switch ( type ) { case WStype_DISCONNECTED: Serial.printf("[%u] Disconnected!\n", num); if ( numberOfWebsocketClients > 0 ) numberOfWebsocketClients--; updateSidebar = true; break; case WStype_CONNECTED: Serial.printf ( "[%u] Connected from ", num ); Serial.println ( webSocket.remoteIP ( num )); numberOfWebsocketClients++; updateSidebar = true; webSocket.sendTXT ( num, "Connected" ); // send message back to client break; /* * * Message format * #(code) value where code is a single char. Case is important * * a start freq (MHz) - sig gen freq if in sig gen mode * b stop frequency * c centre frequency * s Span * d Local Oscillator Drive * g PreAmpGain/Mode * i IF frequency * m mode * p set actual power to peak value * o RefOut * r request Settings are pushed * A internal attenuation (PE4302) * E external gain ( eg attenuator(-ve) or preamp(+ve) ) * R requested RBW * S Spur reduction Off/On * t trackGen Off/On/Generate * T trackGen output level * f trackgen signal generator frequency * F Sig Gen frequency * L Sig Gen level (dBm) * G Sig Gen Off/On * P Sig Gen set max output level (dBm) * * Other formats are ignored */ case WStype_TEXT: if ( payload[0] == '#' ) { if ( payloadLength > 3 ) { char* field = strtok ( (char*) &payload[3], " " ); // Extract the field value as string float value = atof ( field ); // Convert to float if ( isnan ( value )) // If not a number return; // Bail out! Serial.printf ( "payload command %c value %f\n", payload[1], value ); switch ( payload[1] ) { case 'a': switch (setting.Mode) { case SA_LOW_RANGE: SetSweepStart ( value * 1000000.0 ); // Set Low range sweep start frequency break; case SIG_GEN_LOW_RANGE: SetSweepStart ( value * 1000000.0 ); // Set Low range sweep start frequency break; case IF_SWEEP: SetIFsweepStart ( value * 1000000.0 ); // Set IF sweep start frequency break; case RX_SWEEP: SetRXsweepStart ( value * 1000000.0 ); // Set RX sweep start frequency break; case BANDSCOPE: SetBandscopeStart ( value * 1000000.0 ); // Set sweep start frequency break; } break; case 'b': switch (setting.Mode) { case SA_LOW_RANGE: SetSweepStop ( value * 1000000.0 ); // Set Low range sweep start frequency break; case IF_SWEEP: SetIFsweepStop ( value * 1000000.0 ); // Set IF sweep start frequency break; case RX_SWEEP: SetRXsweepStop ( value * 1000000.0 ); // Set RX sweep start frequency break; } break; case 'c': SetSweepCenter ( value * 1000000.0, WIDE ); // Set sweep center frequency break; case 'd': SetLoDrive ( (uint8_t) value ); break; case 'f': SetTGFreq ( value ); break; case 'g': SetPreampGain( (int) value ); // Set PreAmp gain register break; case 'i': // IF Frequency SetIFFrequency ( (int32_t) ( value * 1000000.0 )); break; case 'm': // Set mode setMode ( (int16_t) ( value )); break; case 'o': // Ref Output (LO GPIO2) SetRefOutput ( (int) value ); break; case 'p': // Adjust actual power level RequestSetPowerLevel( value ); break; case 'r': // request of settings by client switch (setting.Mode) { case (SA_LOW_RANGE): pushSettings(); break; case (IF_SWEEP): pushIFSweepSettings(); break; case (RX_SWEEP): pushRXSweepSettings(); break; case (BANDSCOPE): pushBandscopeSettings(); break; case (SIG_GEN_LOW_RANGE): pushSigGenSettings(); break; default: Serial.println("Invalid mode in Request setting handler - simpleSA_wifi.cpp"); } break; case 's': // Adjust sweep span SetSweepSpan ( (int32_t) ( value * 1000000.0 )); break; #ifdef TG_IF_INSTALLED case 't': // Tracking Generator off/on/generate SetTracking ( (int8_t)value ); break; #endif case 'A': // Internal Attenuation (PE4302) SetAttenuation ( value ); break; case 'E': // External Gain (+ve) or Attenuation (-ve) SetExtGain ( value ); break; case 'F': SetSGFreq ( value ); // Signal Generator Frequency break; case 'G': SetSGState ( (uint16_t)value ); // Signal Generator output on/off break; case 'L': // Signal Generator output power (dBm) SetSGPower ( value ); break; case 'R': // Requested RBW. 0=Auto SetRBW ( value ); Serial.printf("Wifi RBW %f\n", value); break; case 'S': // Spur Reduction on/off SetSpur ( (int8_t)value ); break; #ifdef TG_IF_INSTALLED case 'T': // Tracking Generator output power (dBm) SetTGPower ( value ); break; #endif default: Serial.printf ( "payload[1] was %c\n", payload[1] ); break; } } } else // payload[0] is not '#' { webSocket.sendTXT ( num, "pong" ); // send message back to client to keep connection alive Serial.printf ( "[%u] get Text: %s\n", num, payload ); } break; default: Serial.println ( "Case?" ); break; } } /* * Monitor Wi-Fi connection if it is alive. If not alive then wait until it reconnects. */ void isWiFiAlive ( void ) { if ( WiFi.status() != WL_CONNECTED ) { Serial.print ( "not connected" ); while ( WiFi.status() != WL_CONNECTED ) { Serial.print ( "." ); delay(500); } numberOfReconnects++; millisConnected = millis(); } } /* * Some routines for XML */ char *escapeXML ( char *s ) // Use special codes for some characters { static char b[1024]; b[0] = '\0'; // Null terminator for ( int i = 0; i < strlen(s); i++ ) { switch ( s[i] ) { case '\"': strcat ( b,""" ); break; case '&': strcat (b, "&" ); break; case '<': strcat ( b,"<" ); break; case '>': strcat ( b,">" ); break; default: int l = strlen ( b ); b[l] = s[i]; b[l + 1] = '\0'; break; } } } /* * Add an XML tag with its value to the buffer b */ void addTagNameValue ( char *b, char *_name, char *value ) { strcat ( b,_name ); strcat ( b, "=\"" ); strcat ( b,value ); strcat ( b,"\" " ); } /* * Handle settings that are common to all modes */ void addCommonSettings ( JsonDocument& jDoc ) { jDoc["mType"] = "Settings"; jDoc["mode"] = setting.Mode; jDoc["IF"] = setting.IF_Freq / 1000000.0; jDoc["attenuation"] = setting.Attenuate; jDoc["extGain"] = setting.ExternalGain; jDoc["filterCal"] = bpfCalibrations[bpfIndex]; jDoc["setRBW"] = setting.Bandwidth10; jDoc["bandwidth"] = bandwidth; jDoc["RefOut"] = setting.ReferenceOut; jDoc["Drive"] = setting.Drive; jDoc["sweepPoints"] = sweepPoints; jDoc["spur"] = setting.Spur; jDoc["tg"] = trackGenSetting.Mode; jDoc["tgPower"] = trackGenSetting.Power; jDoc["tgFreq"] = trackGenSetting.Frequency; jDoc["tgMod"] = trackGenSetting.ModulationType; jDoc["tgModFreq"] = trackGenSetting.ModFrequency; jDoc["sg"] = sigGenOutputOn; // jDoc["sgPower"] = sigGenSetting.Power; jDoc["sgMod"] = sigGenSetting.ModulationType; jDoc["sgModFreq"] = sigGenSetting.ModFrequency; jDoc["sgFreq"] = sigGenSetting.Frequency; jDoc["sgCal"] = sigGenSetting.Calibration; jDoc["sgRange"] = ATTENUATOR_RANGE + 11; // SI4432 output can be adjusted over 21dBm but max to SAW filter is 10dBm jDoc["tgCal"] = trackGenSetting.Calibration; jDoc["tgRange"] = ATTENUATOR_RANGE + 21; // no SAW filter so 20dBm output is possible. if ( AGC_On ) jDoc["PreAmp"] = 0x60; // Auto else jDoc["PreAmp"] = setting.PreampGain; // Fixed gain } /* * Push the settings data to the websocket clients * Slightly different for each mode * * Could economise by splitting so common fields are in one function */ void pushSettings () { if ( numberOfWebsocketClients == 0 ) return; size_t capacity = JSON_ARRAY_SIZE ( SCREEN_WIDTH ) + SCREEN_WIDTH*JSON_OBJECT_SIZE ( 2 ) + JSON_OBJECT_SIZE ( 17 ); static DynamicJsonDocument jsonDocument ( capacity ); // buffer for json data to be pushed to the web clients addCommonSettings(jsonDocument); jsonDocument["dispPoints"] = displayPoints; jsonDocument["start"] = setting.ScanStart / 1000.0; jsonDocument["stop"] = setting.ScanStop / 1000.0; jsonDocument["levelOffset"] = setting.LevelOffset; String wsBuffer; if ( wsBuffer ) { serializeJson ( jsonDocument, wsBuffer ); webSocket.broadcastTXT ( wsBuffer ); // Send to all connected websocket clients } else Serial.println ( "No buffer :("); // Serial.printf ( "Push Settings sweepPoints %u\n", sweepPoints ); } /* * Push the settings data to the websocket clients */ void pushIFSweepSettings () { size_t capacity = JSON_ARRAY_SIZE ( SCREEN_WIDTH ) + SCREEN_WIDTH*JSON_OBJECT_SIZE ( 2 ) + JSON_OBJECT_SIZE ( 17 ); static DynamicJsonDocument jsonDocument ( capacity ); // buffer for json data to be pushed to the web clients addCommonSettings(jsonDocument); jsonDocument["dispPoints"] = displayPoints; jsonDocument["start"] = startFreq_IF / 1000.0; jsonDocument["stop"] = stopFreq_IF / 1000.0; jsonDocument["levelOffset"] = setting.LevelOffset; String wsBuffer; if ( wsBuffer ) { serializeJson ( jsonDocument, wsBuffer ); webSocket.broadcastTXT ( wsBuffer ); // Send to all connected websocket clients } else Serial.println ( "No buffer :("); } /* * Push the settings data to the websocket clients */ void pushRXSweepSettings () { size_t capacity = JSON_ARRAY_SIZE ( SCREEN_WIDTH ) + SCREEN_WIDTH*JSON_OBJECT_SIZE ( 2 ) + JSON_OBJECT_SIZE ( 17 ); static DynamicJsonDocument jsonDocument ( capacity ); // buffer for json data to be pushed to the web clients addCommonSettings(jsonDocument); jsonDocument["dispPoints"] = displayPoints; jsonDocument["start"] = startFreq_RX / 1000.0; jsonDocument["stop"] = stopFreq_RX / 1000.0; jsonDocument["levelOffset"] = 0; String wsBuffer; if ( wsBuffer ) { serializeJson ( jsonDocument, wsBuffer ); webSocket.broadcastTXT ( wsBuffer ); // Send to all connected websocket clients } else Serial.println ( "No buffer :("); } /* * Push the settings data to the websocket clients */ void pushBandscopeSettings () { size_t capacity = JSON_ARRAY_SIZE ( SCREEN_WIDTH ) + SCREEN_WIDTH*JSON_OBJECT_SIZE ( 2 ) + JSON_OBJECT_SIZE ( 17 ); static DynamicJsonDocument jsonDocument ( capacity ); // buffer for json data to be pushed to the web clients addCommonSettings(jsonDocument); jsonDocument["dispPoints"] = setting.BandscopePoints; jsonDocument["start"] = setting.BandscopeStart / 1000.0; jsonDocument["stop"] = ( setting.BandscopeStart + setting.BandscopeSpan ) / 1000.0; jsonDocument["levelOffset"] = setting.LevelOffset; String wsBuffer; if ( wsBuffer ) { serializeJson ( jsonDocument, wsBuffer ); webSocket.broadcastTXT ( wsBuffer ); // Send to all connected websocket clients } else Serial.println ( "No buffer :("); } /* * Push the settings data to the websocket clients */ void pushSigGenSettings () { size_t capacity = JSON_ARRAY_SIZE ( SCREEN_WIDTH ) + SCREEN_WIDTH*JSON_OBJECT_SIZE ( 2 ) + JSON_OBJECT_SIZE ( 17 ); static DynamicJsonDocument jsonDocument ( capacity ); // buffer for json data to be pushed to the web clients addCommonSettings(jsonDocument); jsonDocument["dispPoints"] = displayPoints; jsonDocument["start"] = startFreq_IF / 1000.0; jsonDocument["stop"] = stopFreq_IF / 1000.0; jsonDocument["levelOffset"] = setting.LevelOffset; String wsBuffer; if ( wsBuffer ) { serializeJson ( jsonDocument, wsBuffer ); webSocket.broadcastTXT ( wsBuffer ); // Send to all connected websocket clients } else Serial.println ( "No buffer :("); } /* * Prepare a response ready for push to web clients */ /* * On request from web page return the list of valid RBW settings from the * "bandpassFilters" array (found in the ".ino" file). */ void onGetRbwList ( AsyncWebServerRequest *request ) { response = request->beginResponseStream ( "application/json" ); // Serial.println ( "onGetRbwList" ); response->print ( "[" ); // Start of object int filterCount = rcvr.GetBandpassFilterCount (); for ( int i = 0; i < filterCount-1 ; i++ ) // For each element in the bandpassfilters array response->printf ( "{\"bw10\":%i,\"bw\":%5.1f},", rcvr.GetBandpassFilter10(i), (float) rcvr.GetBandpassFilter10(i) / 10.0 ); response->printf ( "{\"bw10\":%i,\"bw\":%5.1f}", rcvr.GetBandpassFilter10(filterCount-1), (float) rcvr.GetBandpassFilter10(filterCount-1) / 10.0 ); response->println ( "]" ); // End of object request->send ( response ); } /* * On request from web page return the list of valid attenuations * In the case of the PE4302 this is 0-31.5 in 0.5db steps, * but we will reduce this to 3dB steps * Insertion loss is about 1.5dB * */ void onGetAttenList ( AsyncWebServerRequest *request ) { response = request->beginResponseStream ( "application/json" ); // Serial.println ( "onGetAttList" ); response->print ( "[" ); // Start of object for ( int i = 0; i < 30 ; i = i + 3 ) // For each possible attenuation response->printf ( "{\"dB\":%i},", i ); response->printf ( "{\"dB\":%i}", 30 ); response->println ( "]" ); // End of object request->send ( response ); } /* * Functions to execute when the user presses buttons on the webpage */ void onDoReboot ( AsyncWebServerRequest *request ) { request->redirect ( "index.html" ); // Redirect to the index page ESP.restart (); } void onGetNameVersion ( AsyncWebServerRequest *request ) { AsyncResponseStream *response = request->beginResponseStream ( "text/xml" ); response->printf ( "" ); response->printf ( "printf ( "Name=\"%s\" ",PROGRAM_NAME ); response->printf ( "Version=\"%s\" ",PROGRAM_VERSION ); response->printf ( "Copyright=\"M0WID\"" ); response->printf ( "/>" ); request->send(response); } void onGetSIDDs ( AsyncWebServerRequest *request ) { char b[1024]; b[0] = '\0'; Serial.println ( "" ); Serial.println ( "Scanning for SSIDs" ); /* * We need to return a blob of XML containing the visible SSIDs */ strcpy ( b, "" ); // Start of XML int n = WiFi.scanComplete (); if ( n == -2 ) WiFi.scanNetworks ( true ); else if ( n ) { for ( int i = 0; i < n; ++i ) { strcat ( b,"" ); Serial.println ( "... " + WiFi.SSID (i) ); } WiFi.scanDelete (); if ( WiFi.scanComplete() == -2 ) WiFi.scanNetworks ( true ); } strcat ( b, "" ); // Complete the XML request->send ( 200, "text/xml", b ); // Send it to the server } /* * Build the web server * the order here is important - put frequent ones at top of list to improve performance */ void buildServer () // We can now configure and start the server { Serial.println ( "Building Server.." ); server.reset (); // Clear any existing settings and events server.on ( "/getRbwList", HTTP_GET, onGetRbwList ); // Set event to return RBW options as JSON array server.on ( "/getAttenList", HTTP_GET, onGetAttenList ); // Set event to return attenuator options as JSON array server.on ( "/getSSIDs", HTTP_GET, onGetSIDDs ); // Set event to return list of SSID as XML server.on ( "/getNameVersion", HTTP_GET, onGetNameVersion );// Set event to return name and version server.on ( "/doReboot", HTTP_GET, onDoReboot ); // Set event to reboot the ESP32 server.serveStatic ( "/", SPIFFS, "/" ).setDefaultFile ( "index.html" ); server.begin (); }