diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 2c27a0eb..449c643b 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -18,6 +18,7 @@ endif() # Panorama Includes target_include_directories(panorama-client PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/external/wxMathPlot_0.2.0/mathplot ) @@ -74,7 +75,7 @@ if(APPLE) # Apply wxWidgets linker flags - don't use separate_arguments to preserve framework syntax string(STRIP "${WX_LINK_FLAGS}" WX_LINK_FLAGS) - target_link_libraries(panorama-client PRIVATE ${WX_LINK_FLAGS}) + target_link_libraries(panorama-client PRIVATE ${WX_LINK_FLAGS} mathplot) message(STATUS "Using wxWidgets prebuilt from ${WXWIDGETS_PREBUILT_PATH}") else() # linux @@ -100,7 +101,7 @@ else() # linux set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WX_CXX_FLAGS}") set(WX_LIBRARIES "${WX_LINK_FLAGS}") - target_link_libraries(panorama-client "${WX_LIBRARIES}") + target_link_libraries(panorama-client "${WX_LIBRARIES}" mathplot) message(STATUS "Using locally built wxWidgets via wx-config: ${wxWidgets_CONFIG_EXECUTABLE}") endif() diff --git a/client/include/client/DataBuffer.hpp b/client/include/client/DataBuffer.hpp index 6f56e3ac..3af69a9b 100644 --- a/client/include/client/DataBuffer.hpp +++ b/client/include/client/DataBuffer.hpp @@ -43,6 +43,8 @@ class DataBuffer : public BufferBase { // Removes extracted portion from buffer std::string extractNextJson(); + buffer_data_t consumeFront(); + // --------------------------- // JSON PARSING diff --git a/client/include/client/buffer_base.hpp b/client/include/client/buffer_base.hpp index 81b5a1fe..5f5a6b09 100644 --- a/client/include/client/buffer_base.hpp +++ b/client/include/client/buffer_base.hpp @@ -98,6 +98,6 @@ class BufferBase { protected: std::list buffer_; mutable std::mutex mutex_; - int MAX_BUFFER_SIZE = 5; + int MAX_BUFFER_SIZE = 500; int FLUSH_THRESHOLD = 50; //percentage }; diff --git a/client/include/client/graph_panel.hpp b/client/include/client/graph_panel.hpp index d8acbbeb..e29f9bcd 100644 --- a/client/include/client/graph_panel.hpp +++ b/client/include/client/graph_panel.hpp @@ -1,23 +1,26 @@ - #pragma once #include -#include -#include -#include +#include +#include +#include +#include +#include class GraphPanel : public wxPanel { public: GraphPanel(wxWindow* parent); + void AddDataPoint(const std::string& sensorName, double value, double timestamp); + void SetVisibleSensors(const std::set& visisble); + private: - // Event handlers - void OnPaint(wxPaintEvent& event); - void OnSize(wxSizeEvent& event); + mpWindow* m_plot; + + //store data for each sensor + std::map>> sensorData_; + std::map sensorLayers_; - // Drawing functions - void DrawBackground(wxDC& dc); - void DrawGrid(wxDC& dc); - void DrawAxes(wxDC& dc); + std::set visibleSensors_; - wxDECLARE_EVENT_TABLE(); + void UpdateGraph(); }; \ No newline at end of file diff --git a/client/include/client/json_writer.hpp b/client/include/client/json_writer.hpp new file mode 100644 index 00000000..133c10f6 --- /dev/null +++ b/client/include/client/json_writer.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "common/panorama_defines.hpp" +#include "rapidjson/document.h" +#include "rapidjson/writer.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/filewritestream.h" // For writing directly to a FILE* stream +#include +#include +#include // For fopen, fclose + +#include "client/DataBuffer.hpp" +#include +#include +#include +#include + + +class DataBuffer; + +class JsonWriter { +public: + JsonWriter(std::shared_ptr dataBuffer, const std::string& exportPath); // constructor + + void start(); + void stop(); + + // returns boolean if has been written to + bool writeToJson(buffer_data_t data); + + rapidjson::Document getDocumentFromData(buffer_data_t data); + +private: + std::time_t previousTimestamp = 0; + std::atomic running_{true}; + std::shared_ptr dataBuffer_; + std::string exportPath; +}; \ No newline at end of file diff --git a/client/include/client/mainframe.hpp b/client/include/client/mainframe.hpp index d1998de8..d5e74de3 100644 --- a/client/include/client/mainframe.hpp +++ b/client/include/client/mainframe.hpp @@ -11,9 +11,11 @@ #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 @@ -68,6 +70,15 @@ class MainFrame : public wxFrame { std::shared_ptr dataBuffer_; wxTextCtrl* messageDisplay_; wxPanel* consolePanel_; + GraphPanel* graphPanel_; + + wxTimer updateTimer_; + std::atomic updatePending_{false}; + size_t displayedMessageCount_ = 0; + size_t displayedBufferCount_ = 0; + + void OnUpdateTimer(wxTimerEvent& event); + }; diff --git a/client/include/common/panorama_defines.hpp b/client/include/common/panorama_defines.hpp index 8880cd4c..330f7455 100644 --- a/client/include/common/panorama_defines.hpp +++ b/client/include/common/panorama_defines.hpp @@ -1,10 +1,13 @@ #pragma once #include +#include typedef struct { float data; // actual value std::time_t timestamp; // date recorded std::string dataunit; // e.g. "kPa", "mL" std::string datatype; // e.g. "temperature", "sound" + std::string sensor; + int sensorID; -} buffer_data_t; \ No newline at end of file +} buffer_data_t; \ No newline at end of file diff --git a/client/src/DataBuffer.cpp b/client/src/DataBuffer.cpp index d27976fa..7156289d 100644 --- a/client/src/DataBuffer.cpp +++ b/client/src/DataBuffer.cpp @@ -56,6 +56,10 @@ size_t DataBuffer::size() const { return BufferBase::size(); } +buffer_data_t DataBuffer::consumeFront() { + return BufferBase::extractNextBuffer(); +} + void DataBuffer::clear() { BufferBase::clear(); } @@ -114,11 +118,16 @@ void DataBuffer::parseAll(/* std::vector &out */) { std::string DataBuffer::toString(const buffer_data_t& buffer_item) { //convert one struct of buffer_ into string + //bool hasUnit = buffer_item.dataunit != "\0"; std::string temp = "{"; temp = temp + "\"datatype\": \"" + buffer_item.datatype + "\", \"data\": " + std::to_string(buffer_item.data) + ", "; + + //if (hasUnit) { temp = temp + "\"dataunit\": \"" + buffer_item.dataunit + "\", "; + //} + temp = temp + "\"timestamp\": " + std::to_string(buffer_item.timestamp); temp += "}"; diff --git a/client/src/graph_panel.cpp b/client/src/graph_panel.cpp index d4b73d65..9ee7bf25 100644 --- a/client/src/graph_panel.cpp +++ b/client/src/graph_panel.cpp @@ -1,109 +1,90 @@ #include "client/graph_panel.hpp" -#include -#include - -wxBEGIN_EVENT_TABLE(GraphPanel, wxPanel) - EVT_PAINT(GraphPanel::OnPaint) - EVT_SIZE(GraphPanel::OnSize) -wxEND_EVENT_TABLE() GraphPanel::GraphPanel(wxWindow* parent) : wxPanel(parent, wxID_ANY) { - SetBackgroundColour(wxColour(255, 255, 255)); - SetBackgroundStyle(wxBG_STYLE_PAINT); // double buffering so smoother -} - -// redraw the panel -void GraphPanel::OnPaint(wxPaintEvent& event) { - wxAutoBufferedPaintDC dc(this); - dc.Clear(); - - DrawBackground(dc); - DrawGrid(dc); - DrawAxes(dc); -} + m_plot = new mpWindow(this, wxID_ANY); + m_plot-> EnableDoubleBuffer(true); + m_plot-> EnableMousePanZoom(true); -// Function: used when the panel is resized to redraw -void GraphPanel::OnSize(wxSizeEvent& event) { - Refresh(); - Update(); + // Create X & Y axes + mpScaleX* xAxis = new mpScaleX(wxT("Time (seconds)"), mpALIGN_BORDER_BOTTOM, true); + mpScaleY* yAxis = new mpScaleY(wxT("value"), mpALIGN_BORDER_LEFT, true); - event.Skip(); -} + xAxis->SetDrawOutsideMargins(false); + yAxis->SetDrawOutsideMargins(false); -// Drawing functions -void GraphPanel::DrawBackground(wxDC& dc) { - wxSize size = GetClientSize(); - - dc.SetBrush(wxBrush(wxColour(220, 220, 220))); + m_plot->AddLayer(yAxis); + m_plot->AddLayer(xAxis); - dc.SetPen(*wxTRANSPARENT_PEN); + m_plot->SetMargins(30, 30, 85, 60); + m_plot->Fit(); - dc.DrawRectangle(0, 0, size.GetWidth(), size.GetHeight()); + wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(m_plot, 1, wxEXPAND); + SetSizer(sizer); } -// Function: to draw grid lines -void GraphPanel::DrawGrid(wxDC& dc) { - wxSize size = GetClientSize(); - - int leftMargin = 60; - int bottomMargin = 40; - int topMargin = 20; - int graphHeight = size.GetHeight() - topMargin - bottomMargin; - int graphWidth = size.GetWidth() - leftMargin - 20; - - dc.SetPen(wxPen(wxColour(90, 90, 90), 1)); - - // horizontal grid lines - for (int i = 0; i <= 4; i++) { - int y = topMargin + (graphHeight * i / 4); - dc.DrawLine(leftMargin, y, leftMargin + graphWidth, y); - } +void GraphPanel::AddDataPoint(const std::string& sensorName, double value, double timestamp){ + sensorData_[sensorName].push_back({timestamp, value}); - // vertical grid lines - for (int i = 0; i <=6; i++) { - int x = leftMargin + (graphWidth * i / 6); - dc.DrawLine(x, topMargin, x, topMargin + graphHeight); + // Keeps the first 100 data points + if (sensorData_[sensorName].size() > 100) { + sensorData_[sensorName].erase(sensorData_[sensorName].begin()); } -} - - -void GraphPanel::DrawAxes(wxDC& dc){ - wxSize size = GetClientSize(); - - int leftMargin = 60; - int bottomMargin = 40; - int topMargin = 20; - int graphHeight = size.GetHeight() - topMargin - bottomMargin; - int graphWidth = size.GetWidth() - leftMargin - 20; + UpdateGraph(); +} - dc.SetPen(wxPen(wxColour(0, 0, 0), 2)); - - // draw y-axis - dc.DrawLine(leftMargin, topMargin, leftMargin, topMargin + graphHeight); - - // draw x-axis - dc.DrawLine(leftMargin, topMargin + graphHeight, leftMargin + graphWidth, topMargin + graphHeight); - - // - //x axis label - for (int i = 0; i < 6; i++) { - int x = leftMargin + (graphWidth * i / 6); - int value = i; - wxString label = wxString::Format("%d", value); - wxSize textSize = dc.GetTextExtent(label); - dc.DrawText(label, x - textSize.GetWidth() / 2, size.GetHeight() - bottomMargin + 5); +void GraphPanel::UpdateGraph(){ + for (auto& pair : sensorLayers_){ + m_plot->DelLayer(pair.second,true); } - - //y axis label - for (int i = 0; i <= 4; i++) { - int y = (topMargin + graphHeight) - (graphHeight * i / 4); - int value = i * 25; - wxString label = wxString::Format("%d", value); - wxSize textSize = dc.GetTextExtent(label); - dc.DrawText(label, leftMargin - textSize.GetWidth() - 5, y - textSize.GetHeight() / 2); + sensorLayers_.clear(); + + // colours for the graph + wxColour colours[] = { + wxColour(255, 0, 0), + wxColour(0, 255, 0), + wxColour(0, 0, 255), + wxColour(255, 102, 178), + wxColour(178, 102, 255) + }; + int colourIndex = 0; + + for(auto& sensorPair : sensorData_){ + const std::string& sensorName = sensorPair.first; + auto& data = sensorPair.second; + + if(!visibleSensors_.empty() && visibleSensors_.count(sensorName) == 0) + continue; + + if (data.empty()) continue; + + std::vector xs, ys; + for(const auto& point : data){ + xs.push_back(point.first); // timestamp + ys.push_back(point.second); // value + } + + mpFXYVector* layer = new mpFXYVector(wxString(sensorName)); + layer->SetData(xs, ys); + layer->SetContinuity(true); + + wxPen pen(colours[colourIndex % 5], 2); + layer->SetPen(pen); + + m_plot->AddLayer(layer); + sensorLayers_[sensorName] = layer; + + colourIndex++; } + m_plot->Fit(); + m_plot->Refresh(); + m_plot->Update(); } +void GraphPanel::SetVisibleSensors(const std::set& visible) { + visibleSensors_ = visible; + UpdateGraph(); +} diff --git a/client/src/json_reader.cpp b/client/src/json_reader.cpp index 40fb9672..2e84a19a 100644 --- a/client/src/json_reader.cpp +++ b/client/src/json_reader.cpp @@ -7,6 +7,7 @@ #include #include #include +#include "common/panorama_utils.hpp" JsonReader::JsonReader() {} @@ -42,10 +43,7 @@ buffer_data_t JsonReader::exportToBuffer(std::string json) { rapidjson::Document doc; rapidjson::ParseResult ok = doc.Parse(json.c_str()); - // if (doc.IsArray()) { - // std::cout << "dwda"; - // } - //std::cout << json << 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; @@ -58,20 +56,46 @@ buffer_data_t JsonReader::exportToBuffer(std::string json) { return ret; } - std::string sensorTypeString ( - doc["sensor"].GetString(), - doc["sensor"].GetStringLength() - ); - std::string sensorUnitString ( - doc["unit"].GetString(), - doc["unit"].GetStringLength() - ); - double sensorValue = doc["value"].GetDouble(); + ret.sensor = ""; + ret.datatype = ""; + ret.dataunit = ""; + ret.data = 0; + ret.timestamp = NULL; - ret.datatype = sensorTypeString.c_str(); - ret.data = sensorValue; - ret.dataunit = sensorUnitString.c_str(); - ret.timestamp = std::time(&ret.timestamp); + if (doc.HasMember("sensor")) { + std::string sensorTypeString ( + doc["sensor"].GetString(), + doc["sensor"].GetStringLength() + ); + ret.sensor = sensorTypeString.c_str(); + } + if (doc.HasMember("dataunit")) { + std::string sensorUnitString ( + doc["dataunit"].GetString(), + doc["dataunit"].GetStringLength() + ); + ret.dataunit = sensorUnitString.c_str(); + } + if (doc.HasMember("data")) { + double sensorValue = doc["data"].GetDouble(); + ret.data = sensorValue; + } + if (doc.HasMember("datatype")) { + std::string sensorUnitString ( + doc["datatype"].GetString(), + doc["datatype"].GetStringLength() + ); + ret.datatype = sensorUnitString.c_str(); + } + if (doc.HasMember("timestamp")) { + ret.timestamp = (long) doc["timestamp"].GetInt(); + } + + + + + + //ret.timestamp = std::time(nullptr); return ret; diff --git a/client/src/json_writer.cpp b/client/src/json_writer.cpp new file mode 100644 index 00000000..0db20719 --- /dev/null +++ b/client/src/json_writer.cpp @@ -0,0 +1,91 @@ +#include "client/json_writer.hpp" +#include "rapidjson/document.h" +#include "rapidjson/writer.h" +#include "rapidjson/stringbuffer.h" +#include "rapidjson/filewritestream.h" // For writing directly to a FILE* stream +#include +#include +#include // For fopen, fclose +using namespace rapidjson; + +JsonWriter::JsonWriter(std::shared_ptr dataBuffer, const std::string& exportPath) + : dataBuffer_(dataBuffer), exportPath(exportPath) {} + +void JsonWriter::start() { + // This function will run in a separate thread and continuously check for new data in the DataBuffer. + while (running_) { + //sleep for 5 milliseconds to avoid busy waiting + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + + if(dataBuffer_->readAll().size() > 0) { + buffer_data_t latestData = dataBuffer_->readAll().back(); // Get the most recent data entry + int written = 0; + + if (latestData.timestamp > previousTimestamp) { // Check if it's new data + written = writeToJson(latestData); + if(written){ + previousTimestamp = latestData.timestamp; // Update the last written timestamp + } else { + std::cout << "Failed to write data to JSON for sensorID: " << latestData.sensorID << std::endl; + } + } + + } + + } +} + +void JsonWriter::stop() { + running_ = false; +} + +bool JsonWriter::writeToJson(buffer_data_t data) { + rapidjson::Document doc = getDocumentFromData(data); + + // open/create document in rundir with name of sensorID + std::string path = exportPath + "/" + std::to_string(data.sensorID); + + FILE* fp = fopen(path.c_str(), "ab"); + if (!fp) { + // file couldnt open - return false + return false; + } + + char writeBuffer[65536]; + FileWriteStream os(fp, writeBuffer, sizeof(writeBuffer)); + + Writer writer(os); // write without indentation but more compact + + doc.Accept(writer); + fputc('\n', fp); + + fclose(fp); + + return true; // stub +} + +rapidjson::Document JsonWriter::getDocumentFromData(buffer_data_t data) { + rapidjson::Document doc; + doc.SetObject(); + rapidjson::Document::AllocatorType& allocator = doc.GetAllocator(); + + doc.AddMember("data", static_cast(data.data), allocator); + + doc.AddMember("timestamp", static_cast(data.timestamp), allocator); + + rapidjson::Value unit; + unit.SetString(data.dataunit.c_str(), static_cast(data.dataunit.length()), allocator); + doc.AddMember("dataunit", unit, allocator); + + rapidjson::Value type; + type.SetString(data.datatype.c_str(), static_cast(data.datatype.length()), allocator); + doc.AddMember("datatype", type, allocator); + + rapidjson::Value sensor; + sensor.SetString(data.sensor.c_str(), static_cast(data.sensor.length()), allocator); + doc.AddMember("sensor", sensor, allocator); + + doc.AddMember("sensorID", data.sensorID, allocator); + + return doc; +} \ No newline at end of file diff --git a/client/src/main.cpp b/client/src/main.cpp index 4025d35f..79ae4063 100644 --- a/client/src/main.cpp +++ b/client/src/main.cpp @@ -12,6 +12,7 @@ #include "client/json_reader.hpp" #include "client/config_manager.hpp" #include "client/data_logger.hpp" +#include "client/json_writer.hpp" #include using namespace std; @@ -149,6 +150,10 @@ class PanoramaClient : public wxApp { return true; } + // --- Create and start JSON writer on separate thread --- + jsonWriter_ = std::make_shared(dataBuffer_, runtimeDir); + jsonWriterThread_ = std::make_unique(&JsonWriter::start, jsonWriter_); + // --- Create view --- MainFrame* w = new MainFrame("Panorama Client", model_, dataBuffer_); w->Show(); @@ -172,6 +177,16 @@ class PanoramaClient : public wxApp { if (tcpClient_) { tcpClient_->stop(); } + + // Clean shutdown of JSON writer + if (jsonWriter_) { + jsonWriter_->stop(); + } + + if (jsonWriterThread_ && jsonWriterThread_->joinable()) { + jsonWriterThread_->join(); + } + return wxApp::OnExit(); } @@ -180,6 +195,8 @@ class PanoramaClient : public wxApp { std::shared_ptr dataLogger_; std::shared_ptr dataBuffer_; std::unique_ptr tcpClient_; + std::shared_ptr jsonWriter_; + std::unique_ptr jsonWriterThread_; }; wxIMPLEMENT_APP(PanoramaClient); diff --git a/client/src/mainframe.cpp b/client/src/mainframe.cpp index c636fe7a..520bc8aa 100644 --- a/client/src/mainframe.cpp +++ b/client/src/mainframe.cpp @@ -6,6 +6,7 @@ #include "client/sensor_manager.hpp" #include "client/sensor.hpp" #include "client/settings_dialog.hpp" +#include "common/panorama_utils.hpp" #include #include #include @@ -17,6 +18,10 @@ MainFrame::MainFrame(const wxString& title, std::shared_ptr model, CreateMenuBar(); + updateTimer_.Bind(wxEVT_TIMER, &MainFrame::OnUpdateTimer, this); + updateTimer_.Start(10); // milliseconds between GUI refreshes + + // Create splitter for layout wxSplitterWindow* mainSplitter = new wxSplitterWindow(this, wxID_ANY); wxSplitterWindow* topSplitter = new wxSplitterWindow(mainSplitter, wxID_ANY); @@ -37,7 +42,7 @@ MainFrame::MainFrame(const wxString& title, std::shared_ptr model, // Graph panel area - GraphPanel* graphPanel = new GraphPanel(rightSplitter); + graphPanel_ = new GraphPanel(rightSplitter); // Create text control for displaying messages (Console) consolePanel_ = new wxPanel(mainSplitter); @@ -55,7 +60,7 @@ MainFrame::MainFrame(const wxString& title, std::shared_ptr model, consolePanel_->SetSizer(consoleSizer); // Assemble splitters - rightSplitter->SplitHorizontally(dataViewPanel, graphPanel, 180); + rightSplitter->SplitHorizontally(dataViewPanel, graphPanel_, 200); rightSplitter->SetMinimumPaneSize(100); rightSplitter->SetSashGravity(0.0); // keeps data panel a 180px, graph takes extra space @@ -84,31 +89,35 @@ MainFrame::MainFrame(const wxString& title, std::shared_ptr model, } void MainFrame::onModelUpdated() { - // Use CallAfter to update GUI seperate from network thread or smthing - CallAfter(&MainFrame::updateMessageDisplay); - CallAfter(&MainFrame::updateDataPanel); + updatePending_.store(true); } void MainFrame::onSensorToggled() { sensorDataGrid->SetActiveSensors(sensorManager_->GetEnabledSensorNames()); + + auto enabledNames = sensorManager_->GetEnabledSensorNames(); + std::set visible(enabledNames.begin(), enabledNames.end()); + graphPanel_->SetVisibleSensors(visible); } void MainFrame::updateMessageDisplay() { + // Append only new messages auto messages = model_->getMessages(); - wxString text; - for (const auto& msg : messages) { - text += wxString::FromUTF8(msg.c_str()) + "\n"; + for (size_t i = displayedMessageCount_; i < messages.size(); ++i) { + messageDisplay_->AppendText(wxString::FromUTF8(messages[i].c_str()) + "\n"); } - - 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 + auto allBuffer = dataBuffer_->readAll(); + size_t i = 0; + for (const auto& entry : allBuffer) { + if (i >= displayedBufferCount_) { + messageDisplay_->AppendText(wxString::FromUTF8(dataBuffer_->toString(entry))); + } + ++i; } - - messageDisplay_->SetValue(text); - messageDisplay_->SetInsertionPointEnd(); + displayedBufferCount_ = allBuffer.size(); } void MainFrame::updateDataPanel() { @@ -129,7 +138,7 @@ void MainFrame::updateDataPanel() { if (dataBuffer_->size() > 0) { - for (buffer_data_t latestData : dataBuffer_->readAll()) { + for (buffer_data_t latestData : dataBuffer_->consume()) { // Skip entries with no data-type (first sensor reading) if (latestData.datatype.empty()) continue; @@ -144,7 +153,19 @@ void MainFrame::updateDataPanel() { sensorDataGrid->UpdateReading(latestData.datatype, (double)latestData.data, latestData.dataunit); + + auto enabledNames = sensorManager_->GetEnabledSensorNames(); + std::set visible(enabledNames.begin(), enabledNames.end()); + graphPanel_->SetVisibleSensors(visible); //std::cout << "Updated " << latestData.datatype << " with value: " << latestData.data << " " << latestData.dataunit << std::endl; + + if(graphPanel_){ + graphPanel_->AddDataPoint( + latestData.datatype, + (double)latestData.data, + (double)latestData.timestamp + ); + } } } } @@ -235,3 +256,10 @@ void MainFrame::OnSettingsOpen(wxCommandEvent& event) { SettingsDialog dialog(this); dialog.ShowModal(); } + +void MainFrame::OnUpdateTimer(wxTimerEvent&) { + if (updatePending_.exchange(false)) { + updateMessageDisplay(); + updateDataPanel(); + } +} diff --git a/scripts/run.sh b/scripts/run.sh index 5b1235ec..7b556f58 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -1,5 +1,8 @@ #!/usr/bin/env bash +unset GTK_PATH + + SCRIPT_DIR=$(dirname "$(realpath "$0")") ROOT_DIR=$(realpath "$SCRIPT_DIR/..") diff --git a/tools/pserver/pstream_json.py b/tools/pserver/pstream_json.py index 79972295..739fa897 100644 --- a/tools/pserver/pstream_json.py +++ b/tools/pserver/pstream_json.py @@ -11,24 +11,36 @@ def __init__(self): self.counter = 0 self.sample_data = [ { - "sensor": "temperature", - "value": 25.5, - "unit": "celsius" + "data": 25.5, + "timestamp": 123032032, + "dataunit": "K", + "datatype": "temperature", + "sensor:": "TM1000", + "sensorID": 1 }, { - "sensor": "humidity", - "value": 60.2, - "unit": "percent" + "data": 60.2, + "timestamp": 123032032, + "dataunit": "watercontent", + "datatype": "humidity", + "sensor:": "HD1000", + "sensorID": 2 }, { - "sensor": "pressure", - "value": 1013.25, - "unit": "hPa" + "data": 1013.25, + "timestamp": 123032032, + "dataunit": "hPa", + "datatype": "pressure", + "sensor:": "PP1000", + "sensorID": 3 }, { - "sensor": "light", - "value": 0.2, - "unit": "nm" + "data": 0.2, + "timestamp": 123032032, + "dataunit": "nm", + "datatype": "light", + "sensor:": "NM1000", + "sensorID": 4 } ] @@ -39,16 +51,16 @@ def get_next_data(self) -> bytes: json_obj = self.sample_data[data_index].copy() # Add timestamp and sequence number - json_obj["timestamp"] = time.time() + json_obj["timestamp"] = int(time.time()) json_obj["sequence"] = self.counter # Add variation to the values - if json_obj["sensor"] == "temperature": - json_obj["value"] = 25.5 + (self.counter % 10) * 0.5 - elif json_obj["sensor"] == "humidity": - json_obj["value"] = 60.2 + (self.counter % 10) * 0.3 - elif json_obj["sensor"] == "pressure": - json_obj["value"] = 1013.25 + (self.counter % 10) * 0.1 + if json_obj["datatype"] == "temperature": + json_obj["data"] = 25.5 + (self.counter % 10) * 0.5 + elif json_obj["datatype"] == "humidity": + json_obj["data"] = 60.2 + (self.counter % 10) * 0.3 + elif json_obj["datatype"] == "pressure": + json_obj["data"] = 1013.25 + (self.counter % 10) * 0.1 self.counter += 1 diff --git a/tools/pserver/pstreamer.py b/tools/pserver/pstreamer.py index 0aa1af38..58fece92 100644 --- a/tools/pserver/pstreamer.py +++ b/tools/pserver/pstreamer.py @@ -122,7 +122,7 @@ def is_running(self) -> bool: streamer = PStreamer() # Stream builder class # Building JSON stream using the PStreamJSON class - streamer.build_stream(PStreamJSON()).set_interval(1.0) + streamer.build_stream(PStreamJSON()).set_interval(0.054) streamer.start()