diff --git a/.gitignore b/.gitignore index 02595cce..902a4c81 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,8 @@ __pycache__/ firmware/panorama/.cache/ # Runtime directory -rundir/ \ No newline at end of file +rundir/ + + +# Firmware related: +firmware/panorama/.cache/ \ No newline at end of file diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 449c643b..76484d94 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -8,6 +8,12 @@ file(GLOB CLIENT_HEADERS CONFIGURE_DEPENDS include/client/*.hpp include/client/* set(WXMATHPLOT_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) add_subdirectory(external/wxMathPlot_0.2.0/mathplot) +option(BENCHMARK "ENABLE" OFF) + +if (BENCHMARK) + add_compile_definitions(BENCHMARK) +endif() + # Executable settings if(APPLE) add_executable(panorama-client MACOSX_BUNDLE ${CLIENT_SOURCES} ${COMMON_HEADERS} ${CLIENT_HEADERS}) @@ -79,7 +85,7 @@ if(APPLE) message(STATUS "Using wxWidgets prebuilt from ${WXWIDGETS_PREBUILT_PATH}") else() # linux - + find_program(wxWidgets_CONFIG_EXECUTABLE wx-config) #set(wxWidgets_CONFIG_EXECUTABLE "${WXWIDGETS_PREBUILT_PATH}/bin/wx-config") diff --git a/client/include/client/DataBuffer.hpp b/client/include/client/DataBuffer.hpp index 3af69a9b..8e70eb3f 100644 --- a/client/include/client/DataBuffer.hpp +++ b/client/include/client/DataBuffer.hpp @@ -63,6 +63,8 @@ class DataBuffer : public BufferBase { std::string toStringAll(); + void printAll(); + void exportBuffer(std::string exportPath); private: diff --git a/client/include/client/buffer_base.hpp b/client/include/client/buffer_base.hpp index 5f5a6b09..706277d5 100644 --- a/client/include/client/buffer_base.hpp +++ b/client/include/client/buffer_base.hpp @@ -99,5 +99,5 @@ class BufferBase { std::list buffer_; mutable std::mutex mutex_; int MAX_BUFFER_SIZE = 500; - int FLUSH_THRESHOLD = 50; //percentage + int FLUSH_THRESHOLD = 100; //percentage }; diff --git a/client/include/client/command_processor.hpp b/client/include/client/command_processor.hpp new file mode 100644 index 00000000..a84eacba --- /dev/null +++ b/client/include/client/command_processor.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "client/DataBuffer.hpp" +#include "client/post_processing.hpp" + +#include +#include +#include +#include + +class CommandProcessor { +public: + CommandProcessor(std::shared_ptr dataBuffer, std::shared_ptr postProcessor); + void start(); //run command loop + void stop(); + +private: + std::shared_ptr dataBuffer_; + std::shared_ptr postProcessor_; + std::string command; + std::atomic running_{true}; + + void processCommand(const std::string& command); +}; + diff --git a/client/include/client/data_filters.hpp b/client/include/client/data_filters.hpp new file mode 100644 index 00000000..95af369e --- /dev/null +++ b/client/include/client/data_filters.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +class DataFilters { +public: + DataFilters(); + + int KalmanFilter(double input); + double MovingAverageFilter(double input); + +private: + size_t MAX_KALMAN_SIZE = 10; + std::vector kalmanList; + + size_t MAX_MOVINGAVERAGE = 10; + std::deque movingAverageList; + +}; \ No newline at end of file diff --git a/client/include/client/graph_panel.hpp b/client/include/client/graph_panel.hpp index e49f20eb..547f9f58 100644 --- a/client/include/client/graph_panel.hpp +++ b/client/include/client/graph_panel.hpp @@ -5,10 +5,12 @@ #include #include #include +#include +#include "client/post_processing.hpp" class GraphPanel : public wxPanel { public: - GraphPanel(wxWindow* parent); + GraphPanel(wxWindow* parent, std::shared_ptr postProcessor); void AddDataPoint(const std::string& sensorName, double value, double timestamp); void SetVisibleSensors(const std::set& visisble); @@ -30,4 +32,6 @@ class GraphPanel : public wxPanel { void UpdateGraph(); wxDECLARE_EVENT_TABLE(); + + std::shared_ptr postProcessor_; }; \ No newline at end of file diff --git a/client/include/client/mainframe.hpp b/client/include/client/mainframe.hpp index 249c7b99..c08ee9e3 100644 --- a/client/include/client/mainframe.hpp +++ b/client/include/client/mainframe.hpp @@ -10,12 +10,14 @@ #include #include #include +#include #include #include #include "client/sensor_data_panel.h" #include "client/sensor_manager.hpp" #include "client/sensor.hpp" #include "client/graph_panel.hpp" +#include "client/post_processing.hpp" #include class MessageModel; @@ -45,7 +47,7 @@ class MainFrame : public wxFrame { }; MainFrame(const wxString& title, std::shared_ptr model, - std::shared_ptr dataBuffer, + std::shared_ptr dataBuffer, std::shared_ptr postProcessor, TcpClient* tcpClient, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize(1200, 800)); @@ -91,12 +93,16 @@ class MainFrame : public wxFrame { void OnUpdateTimer(wxTimerEvent& event); + std::shared_ptr postProcessor_; + // Auto detecting ESP32s std::unique_ptr esp32Scanner_; wxPanel* esp32Banner_ = nullptr; wxBoxSizer* mainSizer_ = nullptr; std::atomic esp32BannerPending_{false}; - bool esp32BannerVisible_ = false; + bool esp32BannerVisible_ = false; + + }; #endif // __MAINFRAME__ \ No newline at end of file diff --git a/client/include/client/post_processing.hpp b/client/include/client/post_processing.hpp new file mode 100644 index 00000000..501aea85 --- /dev/null +++ b/client/include/client/post_processing.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +class PostProcessing { +public: + PostProcessing(); + float processData(float data); + void reset(); + void addOffset(float offset); + void addScaling(float scaleFactor); + void updateDataBase(); + +private: + float currentOffset = 0.0; + float currentScaleFactor = 1.0; + std::mutex mutex_; + +}; \ No newline at end of file diff --git a/client/include/client/tcp_client.hpp b/client/include/client/tcp_client.hpp index e6b6a744..54534ecb 100644 --- a/client/include/client/tcp_client.hpp +++ b/client/include/client/tcp_client.hpp @@ -33,11 +33,21 @@ class TcpClient { std::atomic running_; std::thread clientThread_; + + #ifdef _WIN32 unsigned long long socket_; // SOCKET type on Windows #else int socket_; #endif + +#ifdef BENCHMARK + // track bitrate + std::time_t prevRefresh_ = std::time(nullptr); + double dataPerSecond_ = 0.0; + int dataCount_ = 0; + +#endif }; #endif \ No newline at end of file diff --git a/client/src/DataBuffer.cpp b/client/src/DataBuffer.cpp index 7156289d..c0bba6f5 100644 --- a/client/src/DataBuffer.cpp +++ b/client/src/DataBuffer.cpp @@ -12,19 +12,10 @@ DataBuffer::~DataBuffer() { } void DataBuffer::writeData(buffer_data_t jsonChunk) { - // Append the new chunk of raw JSON data to the buffer. This functions only job is to store raw - // inbound data in a way that doesn't lose anything later the parser will call extractNextJson() - // parseNextJson() - // std::cout << "[DataBuffer] Writing data to buffer:" << std::endl; - // std::cout << "[DataBuffer] a: '" << jsonChunk.a << "' a_data: " << jsonChunk.a_data << std::endl; - // std::cout << "[DataBuffer] b: '" << jsonChunk.b << "' b_data: " << jsonChunk.b_data << std::endl; - // std::cout << "[DataBuffer] Buffer size before write: " << size() << std::endl; - - //get the runtime directory path from DataLogger to export buffer if it exceeds threshold - - //DataLogger logger; - //std::string exportPath = logger.getLogFilePath(); + //before writing, do necessary post processing on jsonChunk + //jsonChunk.data = PostProcessing.processData(jsonChunk.data); + write(jsonChunk); if ((int)size() > FLUSH_THRESHOLD * MAX_BUFFER_SIZE / 100) { @@ -32,9 +23,7 @@ void DataBuffer::writeData(buffer_data_t jsonChunk) { exportBuffer(logFilePath_); } - // std::cout << "[DataBuffer] Buffer size after write: " << size() << std::endl; - // std::cout << buffer_.size(); - // std::cout << "[DataBuffer] : " << toStringAll() << std::endl; + } void DataBuffer::setData(buffer_data_t jsonData) { @@ -153,6 +142,11 @@ std::string DataBuffer::toStringAll() { return res; } +void DataBuffer::printAll() { + //print all buffer_ as string + std::cout << toStringAll() << std::endl; +} + void DataBuffer::exportBuffer(std::string exportPath) { //Export the entire buffer (make a local JSON file under client/src/) @@ -170,4 +164,32 @@ void DataBuffer::exportBuffer(std::string exportPath) { fputs(json_content.c_str(), fp); fclose(fp); return; -} \ No newline at end of file +} + +/* +void DataBuffer::replaceData(std::string dataType, float targetValue) { + //replace all data of type dataType in buffer_ with targetValue + std::lock_guard lock(mutex_); + + std::cout << "Replacing data of type " << dataType << " with value " << targetValue << std::endl; + std::cout << "Buffer before replaceData: " << toStringAll() << std::endl; + + for (auto& item : readAll()) { + if (item.datatype == dataType) { + item.data = targetValue; + std::cout << "Replaced data of type " << dataType << " with value " << targetValue << std::endl; + } + } +} + +void DataBuffer::addOffset(std::string dataType, float offsetValue) { + //add offsetValue to all data of type dataType in buffer_ + std::lock_guard lock(mutex_); + + for (auto& item : readAll()) { + if (item.datatype == dataType) { + item.data += offsetValue; + } + } +} +*/ \ No newline at end of file diff --git a/client/src/command_processor.cpp b/client/src/command_processor.cpp new file mode 100644 index 00000000..e48d1bbe --- /dev/null +++ b/client/src/command_processor.cpp @@ -0,0 +1,72 @@ +#include "client/command_processor.hpp" +#include "client/DataBuffer.hpp" + +/* +TO DO: add stop mechanism for command processor thread, currently it runs indefinitely and can only be stopped by exiting the program +TO DO: add command_processor in onExit() in main.cpp +TO DO: handle race conditions between command processor and tcp client both accessing data buffer using mutex +*/ + +CommandProcessor::CommandProcessor(std::shared_ptr dataBuffer, std::shared_ptr postProcessor) + : dataBuffer_(dataBuffer), postProcessor_(postProcessor) { + +} + +void CommandProcessor::start() { + while(running_) { + std::cout << "Enter command (type 'exit' to quit): "; + std::getline(std::cin, command); + + if (command == "exit") { + break; + } + + + processCommand(command); + } +} + +void CommandProcessor::stop() { + running_ = false; +} + +void CommandProcessor::processCommand(const std::string& command) { + /* + command: + + */ + std::string cmdType = command.substr(0, command.find(' ')); + + //get the parameters from command + size_t firstSpace = command.find(' '); + size_t secondSpace = command.find(' ', firstSpace + 1); + std::string firstParameter = command.substr(firstSpace + 1, secondSpace - firstSpace - 1); + std::string secondParameter = command.substr(secondSpace + 1); + + if(cmdType == "printAll") { + dataBuffer_->printAll(); + + } else if (cmdType == "clear") { + dataBuffer_->clear(); + + } else if (cmdType == "size"){ + std::cout << "Buffer size: " << dataBuffer_->size() << std::endl; + + } else if (cmdType == "reset"){ + //reset all post processing parameters to default values + postProcessor_->reset(); + + } else if (cmdType == "setOffset"){ + float offset = std::stof(firstParameter); + postProcessor_->addOffset(offset); + + } else if (cmdType == "setScale"){ + float scaleFactor = std::stof(firstParameter); + postProcessor_->addScaling(scaleFactor); + + } else { + std::cout << "Unknown command: " << command << std::endl; + } + + //add more commands as needed +} \ No newline at end of file diff --git a/client/src/data_filters.cpp b/client/src/data_filters.cpp new file mode 100644 index 00000000..7dc4d7f9 --- /dev/null +++ b/client/src/data_filters.cpp @@ -0,0 +1,28 @@ +#include "client/data_filters.hpp"; + + +DataFilters::DataFilters() { +} + + +int DataFilters::KalmanFilter(double input) { + + // QUEUE DATA TYPE, FIRST IN FIRST OUT + return 0.0; +} + +double DataFilters::MovingAverageFilter(double input) { + movingAverageList.push_front(input); + if (MAX_MOVINGAVERAGE <= movingAverageList.size()) { + movingAverageList.pop_back(); + } + + double sum = 0; + int i = 0; + while (i < movingAverageList.size()) { + sum += movingAverageList[i]; + i++; + } + + return sum / i; +} \ No newline at end of file diff --git a/client/src/graph_panel.cpp b/client/src/graph_panel.cpp index 82153b91..653c21bf 100644 --- a/client/src/graph_panel.cpp +++ b/client/src/graph_panel.cpp @@ -9,8 +9,8 @@ wxBEGIN_EVENT_TABLE(GraphPanel, wxPanel) EVT_SIZE(GraphPanel::OnSize) wxEND_EVENT_TABLE() -GraphPanel::GraphPanel(wxWindow* parent) - : wxPanel(parent, wxID_ANY) { +GraphPanel::GraphPanel(wxWindow* parent, std::shared_ptr postProcessor) + : wxPanel(parent, wxID_ANY), postProcessor_(postProcessor) { m_plot = new mpWindow(this, wxID_ANY); m_plot-> EnableDoubleBuffer(true); @@ -144,7 +144,7 @@ void GraphPanel::UpdateGraph(){ m_plot->DelLayer(pair.second,true); } sensorLayers_.clear(); - + // colours for the graph wxColour colours[] = { wxColour(255, 0, 0), @@ -167,7 +167,11 @@ void GraphPanel::UpdateGraph(){ std::vector xs, ys; for(const auto& point : data){ xs.push_back(point.first); // timestamp - ys.push_back(point.second); // value + + //add necessary offset and scaling to the data point before plotting + auto value = point.second; + value = postProcessor_->processData(value); + ys.push_back(value); } mpFXYVector* layer = new mpFXYVector(wxString(sensorName)); diff --git a/client/src/json_reader.cpp b/client/src/json_reader.cpp index a1e79834..61b443bb 100644 --- a/client/src/json_reader.cpp +++ b/client/src/json_reader.cpp @@ -43,7 +43,7 @@ buffer_data_t JsonReader::exportToBuffer(std::string json) { rapidjson::Document doc; rapidjson::ParseResult ok = doc.Parse(json.c_str()); - std::cout << json.c_str() << std::endl; + //std::cout << json.c_str() << std::endl; if (!ok) { std::cerr << "JSON parse error at offset " << ok.Offset() << ": " << rapidjson::GetParseError_En(ok.Code()) << std::endl; @@ -101,10 +101,6 @@ buffer_data_t JsonReader::exportToBuffer(std::string json) { ret.timestamp = (long) doc["timestamp"].GetInt(); } - - - - //ret.timestamp = std::time(nullptr); return ret; diff --git a/client/src/main.cpp b/client/src/main.cpp index 7f536bd6..66abb26f 100644 --- a/client/src/main.cpp +++ b/client/src/main.cpp @@ -12,7 +12,9 @@ #include "client/json_reader.hpp" #include "client/config_manager.hpp" #include "client/data_logger.hpp" +#include "client/command_processor.hpp" #include "client/json_writer.hpp" +#include "client/command_processor.hpp" #include using namespace std; @@ -134,16 +136,14 @@ class PanoramaClient : public wxApp { // --- Create DataBuffer --- dataBuffer_ = std::make_shared(runtimeDir + "/data"); + // --- Create and start TCP client on separate thread --- std::string tcpHost; int tcpPort; bool autoReconnect; int reconnectDelay; - if (parser.isNoEspMode()) { - tcpHost = "127.0.0.1"; - tcpPort = 3000; - } else if (!config.getTcpSettings(tcpHost, tcpPort, autoReconnect, reconnectDelay)) { + if (!config.getTcpSettings(tcpHost, tcpPort, autoReconnect, reconnectDelay)) { tcpHost = "127.0.0.1"; tcpPort = 3000; } @@ -161,12 +161,25 @@ class PanoramaClient : public wxApp { return true; } + // // For running while tracking bitrate + // if (parser.isBitrateMode()) { + // cout << "Tracking bitrate information\n"; + // tcpClient_->setTrackBitrate(true); + // } + // --- Create and start JSON writer on separate thread --- jsonWriter_ = std::make_shared(dataBuffer_, runtimeDir); jsonWriterThread_ = std::make_unique(&JsonWriter::start, jsonWriter_); + // --- Create PostProcessing as a shared pointer --- + auto postProcessor = std::make_shared(); + + // --- Create CommandProcessor on a separate thread--- + cmdProcessor_ = std::make_shared(dataBuffer_, postProcessor); + cmdThread_ = std::make_unique(&CommandProcessor::start, cmdProcessor_); + // --- Create view --- - MainFrame* w = new MainFrame("Panorama Client", model_, dataBuffer_, tcpClient_.get()); + MainFrame* w = new MainFrame("Panorama Client", model_, dataBuffer_, postProcessor, tcpClient_.get()); w->Show(); @@ -189,15 +202,31 @@ class PanoramaClient : public wxApp { tcpClient_->stop(); } + //Clean shutdown of command processor + if (cmdProcessor_) { + cmdProcessor_->stop(); + } + if (cmdThread_ && cmdThread_->joinable()) { + cmdThread_->join(); + } + // Clean shutdown of JSON writer if (jsonWriter_) { jsonWriter_->stop(); } - if (jsonWriterThread_ && jsonWriterThread_->joinable()) { jsonWriterThread_->join(); } + + if (cmdProcessor_) { + cmdProcessor_->stop(); + } + + if (cmdThread_ && cmdThread_->joinable()) { + cmdThread_->join(); + } + return wxApp::OnExit(); } @@ -208,6 +237,8 @@ class PanoramaClient : public wxApp { std::unique_ptr tcpClient_; std::shared_ptr jsonWriter_; std::unique_ptr jsonWriterThread_; + std::shared_ptr cmdProcessor_; + std::unique_ptr cmdThread_; }; wxIMPLEMENT_APP(PanoramaClient); diff --git a/client/src/mainframe.cpp b/client/src/mainframe.cpp index 224c9e6d..63f29ad4 100644 --- a/client/src/mainframe.cpp +++ b/client/src/mainframe.cpp @@ -16,10 +16,10 @@ #include MainFrame::MainFrame(const wxString& title, std::shared_ptr model, - std::shared_ptr dataBuffer, + std::shared_ptr dataBuffer, std::shared_ptr postProcessor, TcpClient* tcpClient, const wxPoint& pos, const wxSize& size) - : wxFrame(nullptr, wxID_ANY, title, pos, size), model_(model), dataBuffer_(dataBuffer), tcpClient_(tcpClient) { + : wxFrame(nullptr, wxID_ANY, title, pos, size), model_(model), dataBuffer_(dataBuffer), postProcessor_(postProcessor), tcpClient_(tcpClient) { CreateMenuBar(); @@ -83,7 +83,7 @@ MainFrame::MainFrame(const wxString& title, std::shared_ptr model, // Graph panel area - graphPanel_ = new GraphPanel(rightSplitter); + graphPanel_ = new GraphPanel(rightSplitter, postProcessor_); // Create text control for displaying messages (Console) consolePanel_ = new wxPanel(mainSplitter); @@ -158,6 +158,17 @@ void MainFrame::updateMessageDisplay() { for (size_t i = displayedMessageCount_; i < messages.size(); ++i) { messageDisplay_->AppendText(wxString::FromUTF8(messages[i].c_str()) + "\n"); } + + //print dataBuffer contents + /* + if (dataBuffer_->size() > 0) { + //std::cout << dataBuffer_->toStringAll(); + text += "\n--- DataBuffer Contents ---\n"; + text += wxString::FromUTF8(dataBuffer_->toStringAll()); + text += "--- End of DataBuffer ---\n"; + } + */ + displayedMessageCount_ = messages.size(); // Append only new buffer entries @@ -169,6 +180,8 @@ void MainFrame::updateMessageDisplay() { } ++i; } + + displayedBufferCount_ = allBuffer.size(); } @@ -211,6 +224,7 @@ void MainFrame::updateDataPanel() { graphPanel_->SetVisibleSensors(visible); //std::cout << "Updated " << latestData.datatype << " with value: " << latestData.data << " " << latestData.dataunit << std::endl; + if(graphPanel_){ graphPanel_->AddDataPoint( latestData.datatype, @@ -218,6 +232,8 @@ void MainFrame::updateDataPanel() { (double)latestData.timestamp ); } + + } } } diff --git a/client/src/post_processing.cpp b/client/src/post_processing.cpp new file mode 100644 index 00000000..7f551e7e --- /dev/null +++ b/client/src/post_processing.cpp @@ -0,0 +1,36 @@ +#include "client/post_processing.hpp" + +PostProcessing::PostProcessing() { + +} + +float PostProcessing::processData(float data) { + //std::cout << "currentOffset: " << currentOffset << ", currentScaleFactor: " << currentScaleFactor << std::endl; // Debug output + std::lock_guard lock(mutex_); + return data*currentScaleFactor + currentOffset; +} + +void PostProcessing::addOffset(float offset) { + std::lock_guard lock(mutex_); + currentOffset = offset; //record the offset value + std::cout << "Offset set to: " << offset << std::endl; +} + +void PostProcessing::addScaling(float scaleFactor) { + std::lock_guard lock(mutex_); + currentScaleFactor = scaleFactor; //record the scale factor value + std::cout << "Scale factor set to: " << scaleFactor << std::endl; +} + +void PostProcessing::reset() { + //reset all post processing parameters to default values + addOffset(0); + addScaling(1.0); +} + +void PostProcessing::updateDataBase() { + //TODO: update the database accordingly + + +} + diff --git a/client/src/tcp_client.cpp b/client/src/tcp_client.cpp index 039c39a6..77c729a0 100644 --- a/client/src/tcp_client.cpp +++ b/client/src/tcp_client.cpp @@ -95,8 +95,22 @@ void TcpClient::run() { // Parse JSON and write to DataBuffer buffer_data_t parsedData = reader.exportToBuffer(received); dataBuffer_->writeData(parsedData); - model_->addMessage("Data" + std::to_string(dataBuffer_->size())); + //model_->addMessage("Data" + std::to_string(dataBuffer_->size())); model_->addMessage("Received: " + received); + #ifdef BENCHMARK + // track data???? + dataCount_++; + if (std::difftime(std::time(nullptr), prevRefresh_) >= 1) { + // AT 1 - set to 0 + dataPerSecond_ = dataCount_ / std::difftime(std::time(nullptr), prevRefresh_); + dataCount_ = 0; + prevRefresh_ = std::time(nullptr); + std::cout << ("Data per second: " + std::to_string(dataPerSecond_) + "\n"); + } + + + #endif + } } } diff --git a/firmware/panorama/src/test.cpp b/firmware/panorama/src/test.cpp index e2d9e8b5..e260837f 100644 --- a/firmware/panorama/src/test.cpp +++ b/firmware/panorama/src/test.cpp @@ -9,6 +9,9 @@ const uint16_t PORT = 9000; // Onboard LED pin const int LED_PIN = 2; +// Onboard LED pin +const int LED_PIN = 2; + // HC-SR04 pins (adjust to match your wiring) const int TRIG_PIN = 5; const int ECHO_PIN = 18; @@ -128,13 +131,12 @@ void loop() { String json = "{" - "\"sensor\":\"" + String(s.sensor) + "\"," - "\"dataunit\":\"" + String(s.dataunit) + "\"," - "\"data\":" + String(value, 2) + "," - "\"datatype\":\"" + String(s.datatype) + "\"," - "\"sensorID\":" + String(s.sensorID) + "," + "\"sensor\":\"" + String(SENSOR_NAME) + "\"," + "\"unit\":\"cm\"," + "\"value\":" + String(distanceCm, 2) + "," + "\"sensor_id\":" + String(SENSOR_ID) + "," "\"seq\":" + String(seq++) + "," - "\"timestamp\":" + String(timestamp) + + "\"timestamp_ms\":" + String(timestamp) + "}\n"; client.print(json); diff --git a/scripts/build.sh b/scripts/build.sh index 8d38f63a..ef7fcb33 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -2,14 +2,15 @@ BUILD_TYPE=${1:-Debug} +USE_BENCHMARK=${2:-OFF} + SCRIPT_DIR=$(dirname "$(realpath "$0")") ROOT_DIR=$(realpath "$SCRIPT_DIR/..") BUILD_DIR="$ROOT_DIR/build" - echo "build.sh: [INFO] Configuring project ($BUILD_TYPE)..." -cmake -B "$BUILD_DIR" -S "$ROOT_DIR" -DCMAKE_BUILD_TYPE=$BUILD_TYPE +cmake -B "$BUILD_DIR" -S "$ROOT_DIR" -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBENCHMARK=$USE_BENCHMARK echo "build.sh: [INFO] Building project..." cmake --build "$BUILD_DIR" --parallel diff --git a/scripts/run.sh b/scripts/run.sh index 05ad8654..477e7ab7 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -12,6 +12,7 @@ TOOLS_DIR="$ROOT_DIR/tools" NOESP=false NOGUI=false +BITRATE=false TARGET="panorama-client" for arg in "$@"; do @@ -25,6 +26,9 @@ for arg in "$@"; do -nogui) NOGUI=true ;; + -bitrate) + BITRATE=true + ;; -*) echo "run.sh: [ERROR] Unknown flag: $arg" echo "Usage: run.sh [-noesp|-pserver] [-nogui]" @@ -84,6 +88,12 @@ fi if [[ "$NOGUI" == true ]]; then CMD="$CMD --nogui" fi +if [[ "$BITRATE" == true ]]; then + # CMD="$CMD --bitrate" + # bash "$BUILD_SCRIPT" Release ON + echo "run.sh: Bitrate mode enabled, rebuilding" + bash "$BUILD_SCRIPT" Release ON +fi echo "run.sh: [INFO] Running $TARGET..." exec $CMD diff --git a/tools/client_hasna.py b/tools/client_hasna.py deleted file mode 100644 index 5289897b..00000000 --- a/tools/client_hasna.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 -""" -esp32_client.py ---------------- -A simple TCP client that connects to the ESP32 emulator (server) -and prints telemetry data as it is received. - -Usage: - python esp32_client.py --host localhost --port 7000 -""" - -import socket -import argparse - -def main(): - # Set up command-line arguments - parser = argparse.ArgumentParser(description="Simple ESP32 telemetry client") - parser.add_argument("--host", default="localhost", help="Server host to connect to") - parser.add_argument("--port", type=int, default=7000, help="Server port to connect to") - args = parser.parse_args() - - # Create a TCP socket - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - print(f"Connecting to {args.host}:{args.port} ...") - s.connect((args.host, args.port)) - print("Connected. Listening for telemetry...\n") - - try: - # Keep receiving data from the server - buffer = b"" - while True: - data = s.recv(1024) # Read up to 1024 bytes - if not data: - print("Connection closed by server.") - break - buffer += data - - # Split incoming data by newline (each JSON record ends with \n) - while b"\n" in buffer: - line, buffer = buffer.split(b"\n", 1) - print("<-", line.decode().strip()) # Print decoded JSON line - - except KeyboardInterrupt: - print("\nStopped by user.") - except ConnectionResetError: - print("Server disconnected unexpectedly.") - -if __name__ == "__main__": - main() diff --git a/tools/server_hasna.py b/tools/server_hasna.py deleted file mode 100644 index aec424b4..00000000 --- a/tools/server_hasna.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/usr/bin/env python3 -""" -esp32_emulator_tcp.py ---------------------- - -This script emulates an ESP32 device that sends sensor-like data (telemetry) -over a TCP connection. You can run it either as a SERVER (default) or as a CLIENT. - -- In server mode: it waits for a connection, then sends telemetry data. -- In client mode: it connects to a remote server and sends telemetry data. - -Usage examples: - Server (listens for incoming connection): - python esp32_emulator_tcp.py --host 0.0.0.0 --port 7000 - - Client (connects to a server at a given IP): - python esp32_emulator_tcp.py --client --host 192.168.1.5 --port 7000 -""" - -import socket # Networking (TCP/UDP communication) -import time # For delays and timestamps -import json # To send structured data (JSON format) -import argparse # To handle command-line arguments -import random # To simulate random sensor readings - - -# ------------------------------------------------------------------- -# Function: build_telemetry -# Purpose: Generates a JSON string with fake ESP32-like sensor data -# ------------------------------------------------------------------- -def build_telemetry(counter): - # Create a dictionary representing sensor data - """ - data = { - "device": "esp32-emulator-tcp", # name/type of the device - "ts": int(time.time()), # current timestamp (Unix time) - "counter": counter, # packet counter, increments each send - "temperature_c": round(20 + random.uniform(-2, 2), 2), # random temp - "humidity_pct": round(50 + random.uniform(-5, 5), 1) # random humidity - } - """ - data = "Hello World" - # Convert dictionary to JSON and add newline so each record is on its own line - return json.dumps(data) + "\n" - - -# ------------------------------------------------------------------- -# Function: server_mode -# Purpose: Acts like a TCP server (the ESP32 emulator waits for a client) -# ------------------------------------------------------------------- -def server_mode(host, port, interval): - # Create a TCP socket using IPv4 - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - # Bind socket to address and port - s.bind((host, port)) - # Start listening for incoming connections (allow 1 queued connection) - s.listen(1) - print(f"Listening on {host}:{port} — waiting for client...") - - # Accept a connection (blocking until a client connects) - conn, addr = s.accept() - # Use "with" to ensure connection closes cleanly on exit - with conn: - print("Client connected:", addr) - counter = 0 - try: - # Continuous loop: generate and send telemetry every 'interval' seconds - while True: - # Generate fake telemetry data - line = build_telemetry(counter).encode("utf-8") - # Send it over the socket - conn.sendall(line) - # Print what we sent to the console (for debugging) - print("->", line.decode().strip()) - counter += 1 - # Wait for the next transmission - time.sleep(interval) - - # Handle connection loss - except BrokenPipeError: - print("Client disconnected.") - # Handle Ctrl+C (manual stop) - except KeyboardInterrupt: - print("Stopped by user.") - - -# ------------------------------------------------------------------- -# Function: client_mode -# Purpose: Acts like a TCP client (connects to a server and sends data) -# ------------------------------------------------------------------- -def client_mode(host, port, interval): - # Create a TCP socket - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - print(f"Connecting to {host}:{port} ...") - # Connect to the specified server - s.connect((host, port)) - print("Connected.") - counter = 0 - try: - # Same loop as server: generate and send telemetry repeatedly - while True: - line = build_telemetry(counter).encode("utf-8") - s.sendall(line) - print("->", line.decode().strip()) - counter += 1 - time.sleep(interval) - - except BrokenPipeError: - print("Server disconnected.") - except KeyboardInterrupt: - print("Stopped by user.") - - -# ------------------------------------------------------------------- -# Function: main -# Purpose: Entry point — parses command-line arguments and decides mode -# ------------------------------------------------------------------- -def main(): - # Create an argument parser for command-line options - p = argparse.ArgumentParser(description="ESP32 TCP emulator") - - # Add arguments - p.add_argument("--host", default="0.0.0.0", - help="Host IP to bind or connect to (default: 0.0.0.0 for server)") - p.add_argument("--port", type=int, default=7000, - help="Port number to use (default: 7000)") - p.add_argument("--interval", type=float, default=1.0, - help="Seconds between telemetry sends (default: 1.0)") - p.add_argument("--client", action="store_true", - help="Run in client mode instead of server mode") - - # Parse command-line arguments - args = p.parse_args() - - # Decide which mode to run based on --client flag - if args.client: - client_mode(args.host, args.port, args.interval) - else: - server_mode(args.host, args.port, args.interval) - - -# ------------------------------------------------------------------- -# Standard Python entry point -# ------------------------------------------------------------------- -if __name__ == "__main__": - main()