diff --git a/.gitignore b/.gitignore index 3da96e1..74685b8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch -out/* +out/* \ No newline at end of file diff --git a/data/index.html b/data/index.html new file mode 100644 index 0000000..82bfe6a --- /dev/null +++ b/data/index.html @@ -0,0 +1,42 @@ + + + + + ESP Wi-Fi Manager + + + + + + +
+

LORA SA ESP32 CONFIG

+
+
+
+
+
+

+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +

+
+
+
+
+ + + diff --git a/data/style.css b/data/style.css new file mode 100644 index 0000000..491d8c4 --- /dev/null +++ b/data/style.css @@ -0,0 +1,118 @@ +html { + font-family: Arial, Helvetica, sans-serif; + display: inline-block; + text-align: center; +} + +h1 { + font-size: 1.8rem; + color: white; +} + +p { + font-size: 1.4rem; +} + +.topnav { + overflow: hidden; + background-color: #0A1128; +} + +body { + margin: 0; +} + +.content { + padding: 5%; +} + +.card-grid { + max-width: 800px; + margin: 0 auto; + display: grid; + grid-gap: 2rem; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); +} + +.card { + background-color: white; + box-shadow: 2px 2px 12px 1px rgba(140, 140, 140, .5); +} + +.card-title { + font-size: 1.2rem; + font-weight: bold; + color: #034078 +} + +input[type=submit] { + border: none; + color: #FEFCFB; + background-color: #034078; + padding: 15px 15px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + width: 100px; + margin-right: 10px; + border-radius: 4px; + transition-duration: 0.4s; +} + +input[type=submit]:hover { + background-color: #1282A2; +} + +input[type=text], +input[type=number], +select { + width: 50%; + padding: 12px 20px; + margin: 18px; + display: inline-block; + border: 1px solid #ccc; + border-radius: 4px; + box-sizing: border-box; +} + +label { + font-size: 1.2rem; +} + +.value { + font-size: 1.2rem; + color: #1282A2; +} + +.state { + font-size: 1.2rem; + color: #1282A2; +} + +button { + border: none; + color: #FEFCFB; + padding: 15px 32px; + text-align: center; + font-size: 16px; + width: 100px; + border-radius: 4px; + transition-duration: 0.4s; +} + +.button-on { + background-color: #034078; +} + +.button-on:hover { + background-color: #1282A2; +} + +.button-off { + background-color: #858585; +} + +.button-off:hover { + background-color: #252524; +} diff --git a/data/text.txt b/data/text.txt new file mode 100644 index 0000000..e69de29 diff --git a/eink_src/main.cpp b/eink_src/main.cpp index 71a35a0..ee82ccb 100644 --- a/eink_src/main.cpp +++ b/eink_src/main.cpp @@ -364,7 +364,7 @@ void loop() for (int i = 0; i < SAMPLES_RSSI; i++) { state = radio.setFrequency((float)fr + (float)(rssi_mhz_step * u), - false); // false = no calibration need here + true); // false = no calibration need here int radio_error_count = 0; if (state != RADIOLIB_ERR_NONE) { diff --git a/include/File.h b/include/File.h new file mode 100644 index 0000000..6f502ec --- /dev/null +++ b/include/File.h @@ -0,0 +1,55 @@ +#include "FS.h" +#include + +// Initialize LittleFS +void initLittleFS() +{ + if (!LittleFS.begin(true)) + { + Serial.println("An error has occurred while mounting LittleFS"); + } + Serial.println("LittleFS mounted successfully"); +} + +String readFile(fs::FS &fs, const char *path) +{ + Serial.printf("Reading file: %s\r\n", path); + + File file = fs.open(path); + if (!file || file.isDirectory()) + { + Serial.println("- failed to open file for reading"); + return String(""); + } + String content; + Serial.println("- read from file:"); + while (file.available()) + { + content = file.readStringUntil('\n'); + } + file.close(); + return content; +} + +void writeFile(fs::FS &fs, const char *path, const char *message) +{ + Serial.printf("Writing file: %s\r\n", path); + Serial.printf("Content: %s\r\n", message); + + File file = fs.open(path, FILE_WRITE); + if (!file) + { + Serial.println("- failed to open file for writing"); + return; + } + if (file.print(message)) + { + Serial.println("- file written"); + delay(500); + } + else + { + Serial.println("- write failed"); + } + file.close(); +} diff --git a/include/LiLyGo.h b/include/LiLyGo.h index c9c88ee..1d44603 100644 --- a/include/LiLyGo.h +++ b/include/LiLyGo.h @@ -35,6 +35,10 @@ SX1280 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUS // Default SPI on pins from pins_arduino.h SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN); #endif // end USING_SX1262 +#ifdef USING_LR1121 +// Default SPI on pins from pins_arduino.h +LR1121 radio = new Module(RADIO_CS_PIN, RADIO_DIO9_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN); +#endif // end USING_LR1121 #ifdef USING_SX1276 // Default SPI on pins from pins_arduino.h SX1276 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN); diff --git a/include/WIFI_SERVER.h b/include/WIFI_SERVER.h new file mode 100644 index 0000000..93a715d --- /dev/null +++ b/include/WIFI_SERVER.h @@ -0,0 +1,205 @@ +#include +#include +#include +#include + +// Create AsyncWebServer object on port 80 +AsyncWebServer server(80); + +// Search for parameter in HTTP POST request +const String SSID = "ssid"; +const String PASS = "pass"; +const String IP = "ip"; +const String GATEWAY = "gateway"; +const String FSTART = "fstart"; +const String FEND = "fend"; + +// File paths to save input values permanently +// const char *ssidPath = "/ssid.txt"; + +// Variables to save values from HTML form +String ssid = "LoraSA", pass = "1234567890", ip = "192.168.1.100", + gateway = "192.168.1.1", fstart = "", fend = "", smpls = ""; + +IPAddress localIP; +// Set your Gateway IP address +IPAddress localGateway; +IPAddress subnet(255, 255, 0, 0); + +// Timer variables +unsigned long previousMillis = 0; +const long interval = 10000; // interval to wait for Wi-Fi connection (milliseconds) + +// Initialize WiFi +bool initWiFi() +{ + Serial.println("SSID:" + ssid); + Serial.println("PSWD:" + pass); + Serial.println("IP:" + ip); + Serial.println("SUB:" + subnet); + Serial.println("GATAWAY:" + gateway); + if (ssid == "" || ip == "") + { + Serial.println("Undefined SSID or IP address."); + return false; + } + + WiFi.mode(WIFI_STA); + localIP.fromString(ip.c_str()); + localGateway.fromString(gateway.c_str()); + + if (!WiFi.config(localIP, localGateway, subnet)) + { + Serial.println("STA Failed to configure"); + return false; + } + WiFi.begin(ssid.c_str(), pass.c_str()); + Serial.println("Connecting to WiFi..."); + + unsigned long currentMillis = millis(); + previousMillis = currentMillis; + + while (WiFi.status() != WL_CONNECTED) + { + currentMillis = millis(); + if (currentMillis - previousMillis >= interval) + { + Serial.println("Failed to connect."); + return false; + } + } + + Serial.println(WiFi.localIP()); + return true; +} + +void writeParameterToFile(String value, String file) +{ + // Write file to save value + writeFile(LittleFS, file.c_str(), value.c_str()); +} + +void writeParameterToParameterFile(String param, String value) +{ + String file = String("/" + param + ".txt"); + // Write file to save value + writeParameterToFile(value, file.c_str()); +} + +String readParameterFromParameterFile(String param) +{ + String file = String("/" + param + ".txt"); + return readFile(LittleFS, file.c_str()); +} + +void serverServer() +{ + // Route for root / web page + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) + { request->send(LittleFS, "/index.html", "text/html"); }); + + server.serveStatic("/", LittleFS, "/"); + + server.on("/", HTTP_POST, + [](AsyncWebServerRequest *request) + { + int params = request->params(); + for (int i = 0; i < params; i++) + { + Serial.println("Parameter " + String(i) + ": " + + request->getParam(i)->value()); + } + Serial.println(request->params()); + + String p; + if (request->hasParam(IP, true)) + { + p = request->getParam(IP, true)->value(); + writeParameterToParameterFile(IP, p); + } + + if (request->hasParam(IP, true)) + { + p = request->getParam(IP, true)->value(); + writeParameterToParameterFile(IP, p); + } + + if (request->hasParam(IP, true)) + { + p = request->getParam(IP, true)->value(); + writeParameterToParameterFile(IP, p); + } + + if (request->hasParam(GATEWAY, true)) + { + p = request->getParam(GATEWAY, true)->value(); + writeParameterToParameterFile(GATEWAY, p); + } + + if (request->hasParam(FSTART, true)) + { + p = request->getParam(FSTART, true)->value(); + writeParameterToParameterFile(FSTART, p); + } + + if (request->hasParam(FEND, true)) + { + p = request->getParam(FEND, true)->value(); + writeParameterToParameterFile(FEND, p); + } + + if (request->hasParam("samples", true)) + { + p = request->getParam("samples", true)->value(); + writeParameterToParameterFile("samples", p); + } + + request->send(200, "text/plain", + "Done. ESP will restart, connect to your router and " + "go to IP address: " + + ip); + delay(3000); + ESP.restart(); + }); + + /* // Route to set GPIO state to HIGH + server.on("/on", HTTP_GET, + [](AsyncWebServerRequest *request) + { + digitalWrite(ledPin, HIGH); + request->send(LittleFS, "/index.html", "text/html", false, + processor); + }); + + // Route to set GPIO state to LOW + server.on("/off", HTTP_GET, + [](AsyncWebServerRequest *request) + { + digitalWrite(ledPin, LOW); + request->send(LittleFS, "/index.html", "text/html", false, + processor); + });*/ + server.begin(); +} + +void serverStart() +{ + if (initWiFi()) + { + Serial.println("Setting Secure WIFI (Access Point)"); + serverServer(); + } + else + { + // Connect to Wi-Fi network with default SSID and password + Serial.println("Setting AP (Access Point)"); + // NULL sets an open Access Point + WiFi.softAP("LoraSA", NULL); + + IPAddress IP = WiFi.softAPIP(); + Serial.print("AP IP address: "); + Serial.println(IP); + + serverServer(); + } +} diff --git a/lib/scan/scan.h b/lib/scan/scan.h index 9c17e1c..c826609 100644 --- a/lib/scan/scan.h +++ b/lib/scan/scan.h @@ -27,7 +27,9 @@ constexpr float HI_RSSI_THRESHOLD = -44.0; constexpr float LO_RSSI_THRESHOLD = HI_RSSI_THRESHOLD - 66; // number of samples for RSSI method -#define SAMPLES_RSSI 12 // 21 // +#ifndef SAMPLES_RSSI +#define SAMPLES_RSSI 13 // 21 // +#endif #ifdef USING_SX1280PA #define SAMPLES_RSSI 20 #endif diff --git a/platformio.ini b/platformio.ini index 32ddad0..e85aa6e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,12 +26,15 @@ framework = arduino upload_speed = 921600 monitor_speed = 115200 board_build.f_cpu = 240000000 +board_build.filesystem = littlefs lib_deps = ropg/Heltec_ESP32_LoRa_v3@^0.9.1 bblanchon/ArduinoJson@^7.2.0 + ESP Async WebServer build_flags = -DHELTEC_POWER_BUTTON -DHELTEC + [env:heltec_wifi_lora_32_V3_433] platform = espressif32 @@ -40,8 +43,11 @@ framework = arduino upload_speed = 921600 monitor_speed = 115200 board_build.f_cpu = 240000000 +board_build.filesystem = littlefs lib_deps = ropg/Heltec_ESP32_LoRa_v3@^0.9.1 + bblanchon/ArduinoJson@^7.2.0 + ESP Async WebServer build_flags = -DHELTEC_POWER_BUTTON -DHELTEC @@ -56,11 +62,13 @@ framework = arduino upload_speed = 921600 monitor_speed = 115200 board_build.f_cpu = 240000000 +board_build.filesystem = littlefs lib_deps = ropg/Heltec_ESP32_LoRa_v3@^0.9.1 RadioLib U8g2 XPowersLib + ESP Async WebServer build_flags = -DLILYGO -DT3_S3_V1_2_SX1262 @@ -73,6 +81,36 @@ build_flags = -DARDUINO_LILYGO_T3_S3_V1_X -DARDUINO_USB_MODE=1 +[env:lilygo-T3S3-v1-2-lr1121] +platform = espressif32 +board = t3_s3_v1_x +framework = arduino +upload_speed = 921600 +monitor_speed = 115200 +board_build.f_cpu = 240000000 +board_build.filesystem = littlefs +lib_deps = + ropg/Heltec_ESP32_LoRa_v3@^0.9.1 + RadioLib + U8g2 + XPowersLib + bblanchon/ArduinoJson@^7.2.0 + ESP Async WebServer +build_flags = + -DLILYGO + -DT3_S3_V1_2_LR1121 + -DT3_V1_3_SX1262 + -DARDUINO_LILYGO_T3S3_LR1121 + -DESP32 + -DSAMPLES_RSSI=5 + -DUSING_LR1121 + -DFREQ_BEGIN=2400 + -DFREQ_END=2500 + -DARDUINO_ARCH_ESP32 + -DARDUINO_USB_CDC_ON_BOOT=1 + -DARDUINO_LILYGO_T3_S3_V1_X + -DARDUINO_USB_MODE=1 + [env:lilygo-T3S3-v1-2-sx1280] platform = espressif32 @@ -81,11 +119,13 @@ framework = arduino upload_speed = 921600 monitor_speed = 115200 board_build.f_cpu = 240000000 +board_build.filesystem = littlefs lib_deps = ropg/Heltec_ESP32_LoRa_v3@^0.9.1 RadioLib U8g2 XPowersLib + ESP Async WebServer build_flags = -DLILYGO -DT3_S3_V1_2_SX1280_PA @@ -105,11 +145,13 @@ board = esp32dev framework = arduino upload_speed = 115200 monitor_speed = 115200 +board_build.filesystem = littlefs lib_deps = ropg/Heltec_ESP32_LoRa_v3@^0.9.1 RadioLib U8g2 XPowersLib + ESP Async WebServer build_flags = -DLILYGO -DT3_V1_6_SX1276 diff --git a/src/main.cpp b/src/main.cpp index 2e9bf97..ca7dc3b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,9 +27,18 @@ #ifdef HELTEC #include #endif +#include "FS.h" +#include +#include +#include +#include #include #include +#include "WIFI_SERVER.h" + +#define FORMAT_LITTLEFS_IF_FAILED true + // #define OSD_ENABLED true // #define WIFI_SCANNING_ENABLED true // #define BT_SCANNING_ENABLED true @@ -40,12 +49,14 @@ // public and so will be exposed to the user. This allows direct manipulation of the // library internals. #define RADIOLIB_GODMODE (1) +#define RADIOLIB_CHECK_PARAMS (0) #include #include #include #include #include +#include #ifndef LILYGO #include @@ -136,9 +147,9 @@ int SCAN_RANGES[] = {}; // MHZ per page // to put everything into one page set RANGE_PER_PAGE = FREQ_END - 800 -uint64_t RANGE_PER_PAGE = FREQ_END - FREQ_BEGIN; // FREQ_END - FREQ_BEGIN +uint64_t RANGE_PER_PAGE; // FREQ_END - CONF_FREQ_BEGIN -// To Enable Multi Screen scan +uint64_t CONF_FREQ_END, CONF_FREQ_BEGIN; // To Enable Multi Screen scan // uint64_t RANGE_PER_PAGE = 50; // Default Range on Menu Button Switch @@ -170,16 +181,8 @@ constexpr int WINDOW_SIZE = 15; // if more than 100 it can freeze #define SAMPLES 35 //(scan time = 1294) -#define RANGE (int)(FREQ_END - FREQ_BEGIN) - -#define SINGLE_STEP (float)(RANGE / (STEPS * SCAN_RBW_FACTOR)) - -uint64_t range = (int)(FREQ_END - FREQ_BEGIN); - -uint64_t iterations = RANGE / RANGE_PER_PAGE; - -// uint64_t range_frequency = FREQ_END - FREQ_BEGIN; -uint64_t median_frequency = (FREQ_BEGIN + FREQ_END) / 2; +uint64_t RANGE, range, iterations, median_frequency; +float SINGLE_STEP; // #define DISABLE_PLOT_CHART false // unused @@ -225,11 +228,12 @@ uint64_t ranges_count = 0; int rssi = 0; int state = 0; +int CONF_SAMPLES; #ifdef METHOD_SPECTRAL -constexpr int samples = SAMPLES; +int samples = SAMPLES; #endif #ifdef METHOD_RSSI -constexpr int samples = SAMPLES_RSSI; +int samples = SAMPLES_RSSI; #endif uint8_t result_index = 0; @@ -302,8 +306,8 @@ void osdProcess() // memset(max_step_range, 33, 30); max_bin = 32; - osd.displayString(12, 1, String(FREQ_BEGIN)); - osd.displayString(12, OSD_WIDTH - 8, String(FREQ_END)); + osd.displayString(12, 1, String(CONF_FREQ_BEGIN)); + osd.displayString(12, OSD_WIDTH - 8, String(CONF_FREQ_END)); // Finding biggest in result // Skiping 0 and 32 31 to avoid overflow for (int i = 1; i < MAX_POWER_LEVELS - 3; i++) @@ -340,7 +344,7 @@ void osdProcess() #ifdef OSD_SIDE_BAR { osd.displayString(col, OSD_WIDTH - 7, - String(FREQ_BEGIN + (col * osd_mhz_in_bin)) + "-" + + String(CONF_FREQ_BEGIN + (col * osd_mhz_in_bin)) + "-" + String(max_step_range) + " "); } #endif @@ -370,13 +374,20 @@ struct RadioScan : Scan float RadioScan::getRSSI() { -#ifdef USING_SX1280PA +#if defined(USING_SX1280PA) // radio.startReceive(); // get instantaneous RSSI value // When PR will be merged we can use radi.getRSSI(false); uint8_t data[3] = {0, 0, 0}; // RssiInst, Status, RFU radio.mod->SPIreadStream(RADIOLIB_SX128X_CMD_GET_RSSI_INST, data, 3); return ((float)data[0] / (-2.0)); + +#elif defined(USING_LR1121) + // Try getRssiInst + float rssi; + radio.getRssiInst(&rssi); + // pass the replies + return rssi; #else return radio.getRSSI(false); #endif @@ -395,10 +406,12 @@ void init_radio() { // initialize SX1262 FSK modem at the initial frequency both.println("Init radio"); -#ifdef USING_SX1280PA - state = radio.beginGFSK(FREQ_BEGIN); +#if defined(USING_SX1280PA) + state = radio.beginGFSK(CONF_FREQ_BEGIN); +#elif defined(USING_LR1121) + state = radio.beginGFSK(CONF_FREQ_BEGIN, 4.8F, 5.0F, 156.2F, 10, 16U, 1.7F); #else - state = radio.beginFSK(FREQ_BEGIN); + state = radio.beginFSK(CONF_FREQ_BEGIN); #endif if (state == RADIOLIB_ERR_NONE) { @@ -450,7 +463,7 @@ void init_radio() // calibrate only once ,,, at startup // TODO: check documentation (9.2.1) if we must calibrate in certain ranges #ifdef USING_SX1280PA - state = radio.setFrequency(FREQ_BEGIN); + state = radio.setFrequency(CONF_FREQ_BEGIN); if (state != RADIOLIB_ERR_NONE) { Serial.println("Error:setFrequency:" + String(state)); @@ -462,9 +475,9 @@ void init_radio() } #elif USING_SX1276 // Sets carrier frequency. Allowed values range from 137.0 MHz to 1020.0 MHz. - radio.setFrequency(FREQ_BEGIN); + radio.setFrequency(CONF_FREQ_BEGIN); #else - radio.setFrequency(FREQ_BEGIN, true); + radio.setFrequency(CONF_FREQ_BEGIN, true); #endif delay(50); @@ -625,8 +638,56 @@ void logToSerialTask(void *parameter) void drone_sound_alarm(void *arg, Event &e); +void readConfigFile() +{ + // writeFile(LittleFS, "/text.txt", "{WIFI:{name:\"sdfsdf\", Password:\"sdfsdf\"}"); + ssid = readParameterFromParameterFile(SSID); + Serial.println("SSID: " + ssid); + + pass = readParameterFromParameterFile(PASS); + Serial.println("PASS: " + pass); + + ip = readParameterFromParameterFile(IP); + Serial.println("PASS: " + ip); + + gateway = readParameterFromParameterFile(GATEWAY); + Serial.println("GATEWAY: " + gateway); + + fstart = readParameterFromParameterFile(FSTART); + Serial.println("FSTART: " + fstart); + + fend = readParameterFromParameterFile(FEND); + Serial.println("FEND: " + fend); + + smpls = readParameterFromParameterFile("samples"); + Serial.println("SAMPLES: " + smpls); + + CONF_SAMPLES = (smpls == "") ? samples : atoi(smpls.c_str()); + samples = CONF_SAMPLES; + CONF_FREQ_BEGIN = (fstart == "") ? FREQ_BEGIN : atoi(fstart.c_str()); + CONF_FREQ_END = (fend == "") ? FREQ_END : atoi(fend.c_str()); + + both.println("C FREQ BEGIN:" + String(CONF_FREQ_BEGIN)); + both.println("C FREQ END:" + String(CONF_FREQ_END)); + both.println("C SAMPLES:" + String(CONF_SAMPLES)); + + RANGE_PER_PAGE = CONF_FREQ_END - CONF_FREQ_BEGIN; // FREQ_END - CONF_FREQ_BEGIN + + RANGE = (int)(CONF_FREQ_END - CONF_FREQ_BEGIN); + + SINGLE_STEP = (float)(RANGE / (STEPS * SCAN_RBW_FACTOR)); + + range = (int)(CONF_FREQ_END - CONF_FREQ_BEGIN); + + iterations = RANGE / RANGE_PER_PAGE; + + // uint64_t range_frequency = FREQ_END - CONF_FREQ_BEGIN; + median_frequency = (CONF_FREQ_BEGIN + CONF_FREQ_END) / 2; +} + void setup(void) { + #ifdef LILYGO setupBoards(); // true for disable U8g2 display library delay(500); @@ -670,6 +731,7 @@ void setup(void) pinMode(BUZZER_PIN, OUTPUT); pinMode(REB_PIN, OUTPUT); heltec_setup(); + #ifdef JOYSTICK_ENABLED calibrate_joy(); pinMode(JOY_BTN_PIN, INPUT_PULLUP); @@ -689,10 +751,46 @@ void setup(void) } } + display.clear(); + + both.println("CLICK for WIFI settings."); + + for (int i = 0; i < 200; i++) + { + both.print("."); + + button.update(); + delay(10); + if (button.pressedNow()) + { + both.println("-----------"); + both.println("Starting WIFI-SERVER..."); + // Error here: E (15752) ledc: ledc_get_duty(745): LEDC is not initialized + tone(BUZZER_PIN, 205, 100); + delay(50); + tone(BUZZER_PIN, 205, 500); + tone(BUZZER_PIN, 205, 100); + delay(50); + + serverStart(); + both.println("Ready to Connect: 192.168.4.1"); + delay(600); + break; + } + } + both.print("\n"); + + both.println("Init File System"); + initLittleFS(); + + readConfigFile(); + init_radio(); + #ifndef LILYGO vbat = heltec_vbat(); both.printf("V battery: %.2fV (%d%%)\n", vbat, heltec_battery_percent(vbat)); + delay(1000); #endif // end not LILYGO #ifdef WIFI_SCANNING_ENABLED WiFi.mode(WIFI_STA); @@ -796,8 +894,9 @@ void setup(void) r.trigger_level = TRIGGER_LEVEL; stacked.reset(0, 0, display.width(), display.height()); - bar = new DecoratedBarChart(display, 0, 0, display.width(), 0, FREQ_BEGIN, FREQ_END, - LO_RSSI_THRESHOLD, HI_RSSI_THRESHOLD, r.trigger_level); + bar = new DecoratedBarChart(display, 0, 0, display.width(), 0, CONF_FREQ_BEGIN, + CONF_FREQ_END, LO_RSSI_THRESHOLD, HI_RSSI_THRESHOLD, + r.trigger_level); size_t b = stacked.addChart(bar); @@ -811,9 +910,9 @@ void setup(void) delete[] multiples; - waterChart = - new WaterfallChart(display, 0, WATERFALL_START, display.width(), 0, FREQ_BEGIN, - FREQ_END, r.trigger_level, WATERFALL_SENSITIVITY, model); + waterChart = new WaterfallChart(display, 0, WATERFALL_START, display.width(), 0, + CONF_FREQ_BEGIN, CONF_FREQ_END, r.trigger_level, + WATERFALL_SENSITIVITY, model); size_t c = stacked.addChart(waterChart); stacked.setHeight(c, stacked.height - WATERFALL_START - statusBar->height); @@ -1067,13 +1166,13 @@ void loop(void) } // do the scan - range = FREQ_END - FREQ_BEGIN; + range = CONF_FREQ_END - CONF_FREQ_BEGIN; if (RANGE_PER_PAGE > range) { RANGE_PER_PAGE = range; } - r.fr_begin = FREQ_BEGIN; + r.fr_begin = CONF_FREQ_BEGIN; r.fr_end = r.fr_begin; // 50 is a single-screen range @@ -1157,7 +1256,7 @@ void loop(void) state = radio.setFrequency(freq); #else state = radio.setFrequency(r.current_frequency, - false); // false = no calibration need here + true); // true = no calibration need here #endif int radio_error_count = 0; if (state != RADIOLIB_ERR_NONE) @@ -1206,7 +1305,7 @@ void loop(void) // Spectrum analyzer using getRSSI { LOG("METHOD RSSI"); - uint16_t max_rssi = r.rssiMethod(SAMPLES_RSSI, result, + uint16_t max_rssi = r.rssiMethod(CONF_SAMPLES, result, RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE); if (max_x_rssi[display_x] > max_rssi) @@ -1218,7 +1317,7 @@ void loop(void) // if this code is not executed LORA radio doesn't work // basically SX1262 requires delay - // osd.displayString(12, 1, String(FREQ_BEGIN)); + // osd.displayString(12, 1, String(CONF_FREQ_BEGIN)); // osd.displayString(12, 30 - 8, String(FREQ_END)); // delay(2); diff --git a/src/ui.cpp b/src/ui.cpp index e488060..77bdc58 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -16,6 +16,8 @@ // temporary dirty import ... to be solved durring upcoming refactoring extern unsigned int RANGE_PER_PAGE; +extern uint64_t CONF_FREQ_BEGIN; +extern uint64_t CONF_FREQ_END; extern unsigned int median_frequency; extern unsigned int drone_detected_frequency_start; extern unsigned int drone_detected_frequency_end; @@ -95,7 +97,8 @@ void StatusBar::draw() // Frequency start display.setTextAlignment(TEXT_ALIGN_LEFT); display.drawString(pos_x, text_y, - (r.fr_begin == 0) ? String(FREQ_BEGIN) : String(r.fr_begin)); + (r.fr_begin == 0) ? String(CONF_FREQ_BEGIN) + : String(r.fr_begin)); // Frequency detected display.setTextAlignment(TEXT_ALIGN_CENTER); @@ -106,7 +109,7 @@ void StatusBar::draw() // Frequency end display.setTextAlignment(TEXT_ALIGN_RIGHT); display.drawString(pos_x + width, text_y, - (r.fr_end == 0) ? String(FREQ_END) : String(r.fr_end)); + (r.fr_end == 0) ? String(CONF_FREQ_END) : String(r.fr_end)); } // Status text block @@ -170,11 +173,11 @@ void StatusBar::draw() display.drawString(pos_x, text_y, String(loop_time)); #else display.setTextAlignment(TEXT_ALIGN_LEFT); - display.drawString(pos_x, text_y, String(FREQ_BEGIN)); + display.drawString(pos_x, text_y, String(CONF_FREQ_BEGIN)); #endif display.setTextAlignment(TEXT_ALIGN_RIGHT); - display.drawString(pos_x + width, text_y, String(FREQ_END)); + display.drawString(pos_x + width, text_y, String(CONF_FREQ_END)); } else if (ranges_count > 0) { diff --git a/tft_src/main.cpp b/tft_src/main.cpp index 9eae332..ac220ef 100644 --- a/tft_src/main.cpp +++ b/tft_src/main.cpp @@ -13,11 +13,28 @@ // #define ARDUINO_USB_CDC_ON_BOOT 1 // #define LoRaWAN_DEBUG_LEVEL 0 #include "HT_ST7789spi.h" -#include "global_config.h" +// #include "global_config.h" #include "images.h" -#include "ui.h" +// #include "ui.h" #include #include +#include + +struct Entry +{ + String drone; // Drone name + int fstart; // Fr Start + int fend; // Fr End + int y; // y(vertical) position + uint16_t color; // color +}; + +// Define and initialize the vector +std::vector fpvArray = {{"FPV-ELRS", 160, 350, 100, ST7789_BLUE}, + {"915-ELRS", 700, 1000, 100, ST7789_ORANGE}, + {"FPV433-ELRS", 350, 530, 100, ST7789_YELLOW}, + {"Orlan", 820, 940, 98, ST7789_GREEN}, + {"Zala", 830, 950, 80, ST7789_MAGENTA}}; #define st7789_CS_Pin 39 #define st7789_REST_Pin 40 @@ -70,16 +87,20 @@ constexpr bool DRAW_DETECTION_TICKS = true; // number of samples for RSSI method #define SAMPLES_RSSI 5 // 21 // -#define FREQ_BEGIN 650 -#define DEFAULT_DRONE_DETECTION_LEVEL 90 +#define FREQ_BEGIN 150 +#define FREQ_END 950 +#define BANDWIDTH 467.0 +#define MHZ_PX (float)((float)(FREQ_END - FREQ_BEGIN) / DISPLAY_WIDTH) +#define DEFAULT_DRONE_DETECTION_LEVEL -90 +#define DRONE_LEGEND 1; #define RANGE (int)(FREQ_END - FREQ_BEGIN) -#define SINGLE_STEP (float)(RANGE / (STEPS * SCAN_RBW_FACTOR)) +// #define SINGLE_STEP (float)(RANGE / (STEPS * SCAN_RBW_FACTOR)) uint64_t range = (int)(FREQ_END - FREQ_BEGIN); uint64_t fr_begin = FREQ_BEGIN; -uint64_t fr_end = FREQ_BEGIN; +uint64_t fr_end = FREQ_END; // Feature to scan diapasones. Other frequency settings will be ignored. // int SCAN_RANGES[] = {850890, 920950}; @@ -90,7 +111,7 @@ int SCAN_RANGES[] = {}; // uint64_t RANGE_PER_PAGE = FREQ_END - FREQ_BEGIN; // FREQ_END - FREQ_BEGIN // Override or e-ink -uint64_t RANGE_PER_PAGE = FREQ_BEGIN + DISPLAY_WIDTH; +uint64_t RANGE_PER_PAGE = FREQ_END - FREQ_BEGIN; // FREQ_BEGIN + DISPLAY_WIDTH; uint64_t iterations = RANGE / RANGE_PER_PAGE; @@ -114,7 +135,7 @@ bool waterfall[STEPS], detected_y[STEPS]; // 20 - ??? steps of the waterfall bool first_run, new_pixel, detected_x = false; // drone detection flag bool detected = false; -uint64_t drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL; +int64_t drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL; uint64_t drone_detected_frequency_start = 0; uint64_t drone_detected_frequency_end = 0; uint64_t detection_count = 0; @@ -131,9 +152,24 @@ uint64_t scan_time = 0; uint64_t scan_start_time = 0; #endif -#define WATERFALL_START 115 +// To remove waterfall adjust this and this +#define ZERO_LEVEL 110 // Equal to minimal RSSI +#define ZERO_SHIFT 42 +#define LOWER_LEVEL ZERO_LEVEL + ZERO_SHIFT // 108(zero) - (40 moving down) +#define SPECTR_CHART_STAR_TOP 42; +#define WATERFALL_START 119 #define WATERFALL_END DISPLAY_HEIGHT - 10 - 2 +#ifndef DISABLE_WATERFALL +#define DISABLE_WATERFALL 1 // to disable set to 1 +#endif + +#if DISABLE_WATERFALL == 0 +#define ZERO_LEVEL 110 +#define ZERO_SHIFT 0 +#define LOWER_LEVEL ZERO_LEVEL +#endif + uint64_t x, y, range_item, w = WATERFALL_START, i = 0; int osd_x = 1, osd_y = 2, col = 0, max_bin = 32; uint64_t ranges_count = 0; @@ -247,8 +283,27 @@ void battery() } } -constexpr int lower_level = 108; -constexpr int up_level = 40; +void drawDroneLegend() +{ + // Draw FPV array Names + for (const auto &entry : fpvArray) + { + int pixelStart = (entry.fstart - FREQ_BEGIN) / MHZ_PX; + int pixelEnd = (entry.fend - FREQ_BEGIN) / MHZ_PX; + int length = (pixelEnd - pixelStart); + // Serial.println("Pixel Start: " + String(pixelStart)); + // Serial.println("MHinPIX: " + String(MHZ_PX)); + int median = length / 2; + if (entry.fstart < FREQ_END) + { + st7789->drawFastHLine(pixelStart, entry.y, length, entry.color); + drawText(pixelStart, entry.y - 10, entry.drone, entry.color); + } + } +} + +constexpr int lower_level = LOWER_LEVEL; +constexpr int up_level = SPECTR_CHART_STAR_TOP; int rssiToPix(int rssi) { // Bigger is lower signal @@ -256,11 +311,25 @@ int rssiToPix(int rssi) { return lower_level - 1; } - if (abs(rssi) <= up_level) + if (abs(rssi) <= up_level && lower_level < 130) { return up_level; } - return abs(rssi); + // if chart moved to the bottom + if (lower_level > 130) + { + int returnRssi = abs(rssi - ZERO_SHIFT); + // Serial.println("RSSI: " + String(rssi)); + if (returnRssi >= lower_level) + { + return lower_level - 1; + } + return returnRssi; + } + else + { + return abs(rssi); + } } // @@ -286,7 +355,7 @@ long timeSinceLastModeSwitch = 0; float fr = FREQ_BEGIN, fr_x[STEPS + 5], vbat = 0; // MHz in one screen pix step // END will be Begin + 289 * mhz_step -constexpr int mhz_step = 1; +float mhz_step = MHZ_PX; // TODO: make end_freq // Measure RSS every step constexpr float rssi_mhz_step = 0.33; @@ -301,6 +370,7 @@ int window_max_rssi = -999; int window_max_fr = -999; int max_scan_rssi[STEPS + 2]; int max_history_rssi[STEPS + 2]; +int historical_loops = 50, h = 0; long display_scan_start = 0; long display_scan_end = 0; long display_scan_i_end = 0; @@ -315,6 +385,7 @@ constexpr unsigned int STATUS_BAR_HEIGHT = 5; void loop() { + // Serial.println("Loop"); if (screen_update_loop_counter == 0) { fr_x[x1] = 0; @@ -331,11 +402,20 @@ void loop() fr_x[x1] = fr; int u = 0; - int additional_samples = 10; + int additional_samples = 0; // Clear old data with the cursor ... st7789->drawFastVLine(x1, lower_level, -lower_level + 11, ST7789_BLACK); + // Draw max history line + if (h == historical_loops) + { + st7789->drawLine(x1, rssiToPix(max_history_rssi[x1]), x1, lower_level, + ST7789_BLACK /*gray*/); + // clear history + max_history_rssi[x1] = -999; + } + st7789->drawLine(x1, rssiToPix(max_history_rssi[x1]), x1, lower_level, 12710 /*gray*/); // Fetch samples @@ -351,8 +431,15 @@ void loop() additional_samples--; } - radio.setFrequency((float)fr + (float)(rssi_mhz_step * u), - false); // false = no calibration need here + bool calibrate = true; + float freq = (float)fr + (float)(rssi_mhz_step * u); + if ((int)freq % 10 == 0) + { + calibrate = true; + } + radio.setFrequency(freq, + /*false*/ calibrate); // false = no calibration need here + // Serial.println((float)fr + (float)(rssi_mhz_step * u)); u++; if (rssi_mhz_step * u >= mhz_step) { @@ -363,6 +450,7 @@ void loop() rssi_single_start = millis(); } rssi2 = radio.getRSSI(false); + // Serial.print(" RSSI : " + String(rssi2)); scan_iterations++; if (rssi_single_end == 0) { @@ -383,13 +471,24 @@ void loop() #ifdef PRINT_DEBUG Serial.println(String(fr) + ":" + String(rssi2)); #endif + int lineHeight = 0; + st7789->drawPixel(x1, rssiToPix(rssi2), rssiToColor(abs(rssi2))); st7789->drawPixel(x1, rssiToPix(rssi2) - 1, rssiToColor(abs(rssi2))); st7789->drawPixel(x1, rssiToPix(rssi2) - 2, rssiToColor(abs(rssi2))); + st7789->drawPixel(x1, rssiToPix(rssi2) - 3, rssiToColor(abs(rssi2))); + st7789->drawPixel(x1, rssiToPix(rssi2) - 4, rssiToColor(abs(rssi2))); + + if (true /*draw full line*/) + { + st7789->drawFastVLine(x1, rssiToPix(rssi2), lower_level - rssiToPix(rssi2), + rssiToColor(abs(rssi2))); + } // Draw Update Cursor st7789->drawFastVLine(x1 + 1, lower_level, -lower_level + 11, ST7789_BLACK); st7789->drawFastVLine(x1 + 2, lower_level, -lower_level + 11, ST7789_BLACK); - st7789->drawFastVLine(x1 + 3, lower_level, -lower_level + 11, ST7789_BLACK); + // st7789->drawFastVLine(x1 + 3, lower_level, -lower_level + 11, + // ST7789_BLACK); if (max_scan_rssi[x1] == -999) { @@ -417,10 +516,13 @@ void loop() } } // Writing pixel only if it is bigger than drone detection level - if (abs(max_scan_rssi[x1]) < drone_detection_level) + if (abs(max_scan_rssi[x1]) < abs(drone_detection_level)) { - // Waterfall Pixel - st7789->drawPixel(x1, w, rssiToColor(abs(max_scan_rssi[x1]), true)); + if (DISABLE_WATERFALL == 0) + { + // Waterfall Pixel + st7789->drawPixel(x1, w, rssiToColor(abs(max_scan_rssi[x1]), true)); + } detailed_scan_candidate[(int)fr] = (int)fr; } @@ -431,7 +533,7 @@ void loop() // Draw legend for windows if (x1 % rssi_window_size == 0 || x1 == DISPLAY_WIDTH) { - if (abs(window_max_rssi) < drone_detection_level && window_max_rssi != 0 && + if (abs(window_max_rssi) < abs(drone_detection_level) && window_max_rssi != 0 && window_max_rssi != -999) { y2 = 15; @@ -450,9 +552,15 @@ void loop() window_max_rssi = -999; } - // Waterfall cursor - st7789->drawFastHLine(0, w + 1, DISPLAY_WIDTH, ST7789_BLACK); - st7789->drawFastHLine(0, w + 2, DISPLAY_WIDTH, ST7789_BLACK); + if (DISABLE_WATERFALL == 0) + { + // Waterfall cursor + st7789->drawFastHLine(0, w + 1, DISPLAY_WIDTH, ST7789_BLACK); + if (w < WATERFALL_END) + { + st7789->drawFastHLine(0, w + 2, DISPLAY_WIDTH, ST7789_ORANGE); + } + } // drone detection level line if (x1 % 2 == 0) @@ -470,9 +578,9 @@ void loop() button_pressed_counter = 0; if (button.pressed()) { - drone_detection_level++; - if (drone_detection_level > 107) - drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL - 20; + drone_detection_level--; + if (drone_detection_level < -107) + drone_detection_level = DEFAULT_DRONE_DETECTION_LEVEL + 20; while (button.pressedNow()) { delay(100); @@ -491,6 +599,14 @@ void loop() heltec_deep_sleep(); } + // Drone legend every 1/4 of the screen + if (x1 % (STEPS / 4) == 0) + { +#ifdef DRONE_LEGEND + drawDroneLegend(); +#endif + } + // Main N x-axis full loop end logic if (x1 >= STEPS) { @@ -502,17 +618,28 @@ void loop() #ifdef PRINT_DEBUG Serial.println("Screen End for Output: " + String(screen_update_loop_counter)); #endif + // Doing output only after full scan if (screen_update_loop_counter + 1 == SCANS_PER_DISPLAY) { +#ifdef DRONE_LEGEND + drawDroneLegend(); +#endif + + h++; + if (h == historical_loops - 1) + { + h = 0; + } + // Scan results to max Mhz and dB in window display_scan_end = millis(); st7789->fillRect(0, 0, DISPLAY_WIDTH, 11, ST7789_BLACK); drawText(0, 0, "T:" + String(display_scan_end - display_scan_start) + "/" + - String(rssi_single_end - rssi_single_start) + " L:-" + - String(drone_detection_level) + "dB", + String(rssi_single_end - rssi_single_start) + + " L:" + String(drone_detection_level) + "dB", ST7789_BLUE); /// battery();