From 8fd848360a78cd02856797cfa65dac7934149c80 Mon Sep 17 00:00:00 2001 From: Andrew Guz Date: Thu, 25 Sep 2025 21:52:20 +0300 Subject: [PATCH 01/10] Add custom commands to scenario --- CMakeLists.txt | 2 +- src/UniversalDeviceLib/events/Scenario.hpp | 7 +++++-- src/UniversalDeviceLib/marshaling/Marshaling.cpp | 4 ++++ .../marshaling/ScenarioMarshaling.cpp | 9 +++++---- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 38420b8d..1917fbc9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ cmake_minimum_required(VERSION 3.18) set(PROJECT_NAME "UniversalDevice") # name of a project -project(${PROJECT_NAME} VERSION 2.0.0) +project(${PROJECT_NAME} VERSION 2.0.1) # create file with version defines configure_file(Version.hpp.in Version.hpp) diff --git a/src/UniversalDeviceLib/events/Scenario.hpp b/src/UniversalDeviceLib/events/Scenario.hpp index 60fd066f..9f51e0c3 100644 --- a/src/UniversalDeviceLib/events/Scenario.hpp +++ b/src/UniversalDeviceLib/events/Scenario.hpp @@ -3,7 +3,9 @@ #include #include #include +#include +#include "Command.hpp" #include "Uuid.hpp" struct Scenario { @@ -11,9 +13,10 @@ struct Scenario { std::string _name; std::set _activateEvent; std::set _deactivateEvent; + std::unordered_map _commands; bool operator==(const Scenario& other) const { - return std::tie(_id, _name, _activateEvent, _deactivateEvent) == - std::tie(other._id, other._name, other._activateEvent, other._deactivateEvent); + return std::tie(_id, _name, _activateEvent, _deactivateEvent, _commands) == + std::tie(other._id, other._name, other._activateEvent, other._deactivateEvent, other._commands); } }; diff --git a/src/UniversalDeviceLib/marshaling/Marshaling.cpp b/src/UniversalDeviceLib/marshaling/Marshaling.cpp index 8477b738..b4f81c8e 100644 --- a/src/UniversalDeviceLib/marshaling/Marshaling.cpp +++ b/src/UniversalDeviceLib/marshaling/Marshaling.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -617,6 +618,7 @@ void to_json(nlohmann::json& json, const Scenario& scenario) { { "name", scenario._name }, { "activate", scenario._activateEvent }, { "deactivate", scenario._deactivateEvent }, + { "commands", scenario._commands }, }; } @@ -625,6 +627,8 @@ void from_json(const nlohmann::json& json, Scenario& scenario) { scenario._name = json.at("name").get(); scenario._activateEvent = json.at("activate").get>(); scenario._deactivateEvent = json.at("deactivate").get>(); + scenario._commands = + json.count("commands") == 0 ? std::unordered_map{} : json.at("commands").get>(); } void to_json(nlohmann::json& json, const ThermometerLedBrightness& thermometerLedBrightness) { diff --git a/src/UniversalDeviceTests/marshaling/ScenarioMarshaling.cpp b/src/UniversalDeviceTests/marshaling/ScenarioMarshaling.cpp index cce88e23..d0b03c66 100644 --- a/src/UniversalDeviceTests/marshaling/ScenarioMarshaling.cpp +++ b/src/UniversalDeviceTests/marshaling/ScenarioMarshaling.cpp @@ -24,13 +24,14 @@ TEST_CASE("ScenarioJson") { ._name = "test", ._activateEvent = activate, ._deactivateEvent = deactivate, + ._commands = { + { Uuid{}, "command" }, + { Uuid{}, "command2" }, + }, }; const nlohmann::json expectedJson{ - { "id", scenario._id }, - { "name", "test" }, - { "activate", activate }, - { "deactivate", deactivate }, + { "id", scenario._id }, { "name", "test" }, { "activate", activate }, { "deactivate", deactivate }, { "commands", scenario._commands }, }; const nlohmann::json scenarioToJson = static_cast(scenario); From 51c90805f650dadc28fc5311a8a2cce687293bdb Mon Sep 17 00:00:00 2001 From: Andrew Guz Date: Thu, 25 Sep 2025 22:02:31 +0300 Subject: [PATCH 02/10] Small code update --- .../customWidgets/EventWidgetHelper.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/UniversalDeviceFrontendService/customWidgets/EventWidgetHelper.hpp b/src/UniversalDeviceFrontendService/customWidgets/EventWidgetHelper.hpp index dce17dbe..28f0a8c4 100644 --- a/src/UniversalDeviceFrontendService/customWidgets/EventWidgetHelper.hpp +++ b/src/UniversalDeviceFrontendService/customWidgets/EventWidgetHelper.hpp @@ -6,12 +6,12 @@ class EventWidgetHelper final { static void Hide() {} template - static void Hide(T t) { + static void Hide(T& t) { t->hide(); } template - static void Hide(T t, Ts... args) { + static void Hide(T& t, Ts... args) { Hide(t); Hide(args...); } @@ -20,12 +20,12 @@ class EventWidgetHelper final { static void Show() {} template - static void Show(T t) { + static void Show(T& t) { t->show(); } template - static void Show(T t, Ts... args) { + static void Show(T& t, Ts&... args) { Show(t); Show(args...); } From e24eea71e47fa4b36d12e50509021c13aac42178 Mon Sep 17 00:00:00 2001 From: Andrew Guz Date: Sun, 1 Mar 2026 13:15:33 +0300 Subject: [PATCH 03/10] Fix build --- src/UniversalDeviceLib/marshaling/Marshaling.cpp | 2 +- src/UniversalDeviceTests/marshaling/ScenarioMarshaling.cpp | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/UniversalDeviceLib/marshaling/Marshaling.cpp b/src/UniversalDeviceLib/marshaling/Marshaling.cpp index b4f81c8e..d7636a03 100644 --- a/src/UniversalDeviceLib/marshaling/Marshaling.cpp +++ b/src/UniversalDeviceLib/marshaling/Marshaling.cpp @@ -628,7 +628,7 @@ void from_json(const nlohmann::json& json, Scenario& scenario) { scenario._activateEvent = json.at("activate").get>(); scenario._deactivateEvent = json.at("deactivate").get>(); scenario._commands = - json.count("commands") == 0 ? std::unordered_map{} : json.at("commands").get>(); + json.count("commands") == 0 ? std::unordered_map{} : json.at("commands").get>(); } void to_json(nlohmann::json& json, const ThermometerLedBrightness& thermometerLedBrightness) { diff --git a/src/UniversalDeviceTests/marshaling/ScenarioMarshaling.cpp b/src/UniversalDeviceTests/marshaling/ScenarioMarshaling.cpp index d0b03c66..975a3add 100644 --- a/src/UniversalDeviceTests/marshaling/ScenarioMarshaling.cpp +++ b/src/UniversalDeviceTests/marshaling/ScenarioMarshaling.cpp @@ -6,7 +6,9 @@ #include #include "Marshaling.hpp" +#include "RelayState.hpp" #include "Scenario.hpp" +#include "ThermometerLedBrightness.hpp" #include "Uuid.hpp" TEST_CASE("ScenarioJson") { @@ -25,8 +27,8 @@ TEST_CASE("ScenarioJson") { ._activateEvent = activate, ._deactivateEvent = deactivate, ._commands = { - { Uuid{}, "command" }, - { Uuid{}, "command2" }, + { Uuid{}, ThermometerLedBrightness{._brightness = 1, }, }, + { Uuid{}, RelayState{._state = false, }, }, }, }; From c21cda4319852d95d59cb238d583a3d026d24413 Mon Sep 17 00:00:00 2001 From: Andrew Guz Date: Sun, 1 Mar 2026 13:30:55 +0300 Subject: [PATCH 04/10] Update --- .../marshaling/ScenarioMarshaling.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/UniversalDeviceTests/marshaling/ScenarioMarshaling.cpp b/src/UniversalDeviceTests/marshaling/ScenarioMarshaling.cpp index 975a3add..38930889 100644 --- a/src/UniversalDeviceTests/marshaling/ScenarioMarshaling.cpp +++ b/src/UniversalDeviceTests/marshaling/ScenarioMarshaling.cpp @@ -26,9 +26,9 @@ TEST_CASE("ScenarioJson") { ._name = "test", ._activateEvent = activate, ._deactivateEvent = deactivate, - ._commands = { - { Uuid{}, ThermometerLedBrightness{._brightness = 1, }, }, - { Uuid{}, RelayState{._state = false, }, }, + ._commands = { + { Uuid{}, ThermometerLedBrightness{._brightness = 1 } }, + { Uuid{}, RelayState{._state = false } }, }, }; @@ -55,4 +55,14 @@ TEST_CASE("ScenarioJson") { static_cast(vectorScenario), }; REQUIRE(scenariosVectorJson2 == expectedScenariosJson); + + const nlohmann::json scenarioJsonWithNoCommands{ + { "id", scenario._id }, + { "name", "test" }, + { "activate", activate }, + { "deactivate", deactivate }, + }; + + const Scenario scenarioWithNoCommands = scenarioJsonWithNoCommands.get(); + REQUIRE(scenarioWithNoCommands._commands.empty()); } From b2ddffe28c7aa5513d75cb8edc2199f9110d69de Mon Sep 17 00:00:00 2001 From: Andrew Guz Date: Sun, 1 Mar 2026 14:17:09 +0300 Subject: [PATCH 05/10] Update --- .../controllers/ScenariosController.cpp | 14 ++++++- .../controllers/ScenariosController.hpp | 4 +- .../services/Platform.cpp | 2 +- .../controllers/ScenariosControllerTests.cpp | 39 ++++++++++++++----- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/UniversalDeviceServiceLib/controllers/ScenariosController.cpp b/src/UniversalDeviceServiceLib/controllers/ScenariosController.cpp index 0dd225bc..fe804a33 100644 --- a/src/UniversalDeviceServiceLib/controllers/ScenariosController.cpp +++ b/src/UniversalDeviceServiceLib/controllers/ScenariosController.cpp @@ -10,6 +10,8 @@ #include #include "Cache.hpp" +#include "Command.hpp" +#include "CommandsController.hpp" #include "Controller.hpp" #include "DbExtension.hpp" #include "Event.hpp" @@ -21,9 +23,10 @@ #include "Scenario.hpp" #include "Uuid.hpp" -ScenariosController::ScenariosController(IQueryExecutor* queryExecutor, EventsController& eventsController) : +ScenariosController::ScenariosController(IQueryExecutor* queryExecutor, EventsController& eventsController, CommandsController& commandsController) : Controller(queryExecutor), - _eventsController(eventsController) // + _eventsController(eventsController), + _commandsController(commandsController) // { FillCache(); } @@ -141,6 +144,13 @@ bool ScenariosController::ActivateScenario(const Uuid& id) { return false; } + for (const auto& [deviceId, command] : scenario->_commands) { + if (!_commandsController.AddOrUpdate(deviceId, command)) { + LOG_ERROR_MSG("Failed to write command"); + return false; + } + } + if (!_queryExecutor->Commit()) { LOG_ERROR_MSG("Failed to update events for scenario: failed to end transaction"); return false; diff --git a/src/UniversalDeviceServiceLib/controllers/ScenariosController.hpp b/src/UniversalDeviceServiceLib/controllers/ScenariosController.hpp index cc4d5f73..7a2e7727 100644 --- a/src/UniversalDeviceServiceLib/controllers/ScenariosController.hpp +++ b/src/UniversalDeviceServiceLib/controllers/ScenariosController.hpp @@ -4,6 +4,7 @@ #include #include "Cache.hpp" +#include "CommandsController.hpp" #include "Controller.hpp" #include "EventsController.hpp" #include "IQueryExecutor.hpp" @@ -12,7 +13,7 @@ class ScenariosController final : public Controller { public: - ScenariosController(IQueryExecutor* queryExecutor, EventsController& eventsController); + ScenariosController(IQueryExecutor* queryExecutor, EventsController& eventsController, CommandsController& commandsController); ~ScenariosController() = default; @@ -36,4 +37,5 @@ class ScenariosController final : public Controller { private: mutable Cache _cache; EventsController& _eventsController; + CommandsController& _commandsController; }; diff --git a/src/UniversalDeviceServiceLib/services/Platform.cpp b/src/UniversalDeviceServiceLib/services/Platform.cpp index 9b4689f0..d641a33f 100644 --- a/src/UniversalDeviceServiceLib/services/Platform.cpp +++ b/src/UniversalDeviceServiceLib/services/Platform.cpp @@ -46,7 +46,7 @@ Platform::Platform(CrowApp& app, IQueryExecutor* queryExecutor) : _commandsController(queryExecutor), _devicesController(queryExecutor, _settingsController, _commandsController), _eventsController(queryExecutor), - _scenariosController(queryExecutor, _eventsController), + _scenariosController(queryExecutor, _eventsController, _commandsController), _thermometerValuesController(queryExecutor, _devicesController), _relayValuesController(queryExecutor), _motionRelayValuesController(queryExecutor), diff --git a/src/UniversalDeviceTests/controllers/ScenariosControllerTests.cpp b/src/UniversalDeviceTests/controllers/ScenariosControllerTests.cpp index 28fe6aa1..7e53b746 100644 --- a/src/UniversalDeviceTests/controllers/ScenariosControllerTests.cpp +++ b/src/UniversalDeviceTests/controllers/ScenariosControllerTests.cpp @@ -6,10 +6,13 @@ #include #include +#include "Command.hpp" +#include "CommandsController.hpp" #include "Event.hpp" #include "EventUtils.hpp" #include "EventsController.hpp" #include "Provider.hpp" +#include "RelayState.hpp" #include "Scenario.hpp" #include "ScenariosController.hpp" #include "Storage.hpp" @@ -26,7 +29,8 @@ TEST_CASE("ScenariosControllerTests") { Storage storage{ dbPath }; EventsController eventController{ &storage }; - ScenariosController scenariosController{ &storage, eventController }; + CommandsController commandsController{ &storage }; + ScenariosController scenariosController{ &storage, eventController, commandsController }; REQUIRE(scenariosController.List().size() == 0); REQUIRE(scenariosController.Get(Uuid{}).has_value() == false); @@ -44,6 +48,7 @@ TEST_CASE("ScenariosControllerTests") { ._name = "test", ._activateEvent = { GetEventId(event) }, ._deactivateEvent = {}, + ._commands = {}, }; REQUIRE(scenariosController.Add(scenario) == true); @@ -61,6 +66,7 @@ TEST_CASE("ScenariosControllerTests") { ._name = "new", ._activateEvent = { Uuid{}, Uuid{} }, ._deactivateEvent = { Uuid{}, Uuid{} }, + ._commands = {}, }; REQUIRE(scenariosController.Add(scenario2) == true); REQUIRE(scenariosController.List().size() == 2); @@ -93,7 +99,8 @@ TEST_CASE("Scenario activation test") { Storage storage{ dbPath }; EventsController eventsController{ &storage }; - ScenariosController scenariosController{ &storage, eventsController }; + CommandsController commandsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; const auto addEvent = [&eventsController](bool active) -> Uuid { TimerEvent event; @@ -107,11 +114,17 @@ TEST_CASE("Scenario activation test") { const Uuid eventId1 = addEvent(true); const Uuid eventId2 = addEvent(false); + const Uuid deviceId; + const Command command = RelayState{ + ._state = true, + }; - const auto addScenario = [&scenariosController](const std::set& activate, const std::set& deactivate) -> Uuid { + const auto addScenario = [&scenariosController, &deviceId, &command](const std::set& activate, + const std::set& deactivate) -> Uuid { Scenario scenario{ ._activateEvent = activate, ._deactivateEvent = deactivate, + ._commands = { { deviceId, command }, }, }; if (!scenariosController.Add(scenario)) throw std::runtime_error("Failed to create scenario"); @@ -122,27 +135,33 @@ TEST_CASE("Scenario activation test") { const Uuid scenarioId1 = addScenario({ eventId2 }, { eventId1 }); const Uuid scenarioId2 = addScenario({ eventId1, Uuid{} }, { eventId2, Uuid{} }); - const auto checkEventState = [&storage](const Uuid& eventId, bool expectedActivity) { + const auto checkEventState = [&storage, &deviceId, &command](const Uuid& eventId, bool expectedActivity, bool expectCommand) { EventsController eventsController{ &storage }; const std::optional event = eventsController.GetById(eventId); if (!event.has_value()) throw std::runtime_error("Event check failed"); REQUIRE(GetEventActivity(event.value()) == expectedActivity); + + if (expectCommand) { + CommandsController commandsController{ &storage }; + REQUIRE(commandsController.Get(deviceId).has_value()); + REQUIRE(commandsController.Get(deviceId).value() == command); + } }; - checkEventState(eventId1, true); - checkEventState(eventId2, false); + checkEventState(eventId1, true, false); + checkEventState(eventId2, false, false); REQUIRE(scenariosController.ActivateScenario(scenarioId1)); - checkEventState(eventId1, false); - checkEventState(eventId2, true); + checkEventState(eventId1, false, true); + checkEventState(eventId2, true, true); REQUIRE(scenariosController.ActivateScenario(scenarioId2)); - checkEventState(eventId1, true); - checkEventState(eventId2, false); + checkEventState(eventId1, true, true); + checkEventState(eventId2, false, true); } catch (...) { REQUIRE(false); From ad87b9e734b213f910477b01ce70874283e4baa7 Mon Sep 17 00:00:00 2001 From: Andrew Guz Date: Sun, 1 Mar 2026 15:04:26 +0300 Subject: [PATCH 06/10] Update --- .../controllers/ScenariosController.cpp | 16 ++++++++-------- .../controllers/ScenariosController.hpp | 2 +- .../services/ScenariosService.cpp | 11 ++++++++++- .../controllers/ScenariosControllerTests.cpp | 4 ++-- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/UniversalDeviceServiceLib/controllers/ScenariosController.cpp b/src/UniversalDeviceServiceLib/controllers/ScenariosController.cpp index fe804a33..c8d4160f 100644 --- a/src/UniversalDeviceServiceLib/controllers/ScenariosController.cpp +++ b/src/UniversalDeviceServiceLib/controllers/ScenariosController.cpp @@ -113,7 +113,7 @@ void ScenariosController::CleanupScenario(Scenario& scenario) { removeNotExistingEvents(scenario._deactivateEvent, eventIds); } -bool ScenariosController::ActivateScenario(const Uuid& id) { +std::optional ScenariosController::ActivateScenario(const Uuid& id) { std::lock_guard lockGuard{ _mutex }; if (!_cache.Size()) @@ -122,42 +122,42 @@ bool ScenariosController::ActivateScenario(const Uuid& id) { std::optional scenario = _cache.Get(id); if (!scenario.has_value()) { LOG_ERROR_MSG("Scenario not found"); - return false; + return std::nullopt; } CleanupScenario(scenario.value()); if (!_queryExecutor->Begin()) { LOG_ERROR_MSG("Failed to update events for scenario: failed to start transaction"); - return false; + return std::nullopt; } for (const Uuid& activeEventId : scenario->_activateEvent) if (!_eventsController.SetActivity(activeEventId, true).has_value()) { LOG_ERROR_MSG("Failed to set event activity"); - return false; + return std::nullopt; } for (const Uuid& activeEventId : scenario->_deactivateEvent) if (!_eventsController.SetActivity(activeEventId, false).has_value()) { LOG_ERROR_MSG("Failed to set event activity"); - return false; + return std::nullopt; } for (const auto& [deviceId, command] : scenario->_commands) { if (!_commandsController.AddOrUpdate(deviceId, command)) { LOG_ERROR_MSG("Failed to write command"); - return false; + return std::nullopt; } } if (!_queryExecutor->Commit()) { LOG_ERROR_MSG("Failed to update events for scenario: failed to end transaction"); - return false; + return std::nullopt; } LOG_INFO_MSG(fmt::format("Scenario '{}' activated!", scenario->_name)); - return true; + return scenario; } bool ScenariosController::Remove(const Uuid& id) { diff --git a/src/UniversalDeviceServiceLib/controllers/ScenariosController.hpp b/src/UniversalDeviceServiceLib/controllers/ScenariosController.hpp index 7a2e7727..b6c920da 100644 --- a/src/UniversalDeviceServiceLib/controllers/ScenariosController.hpp +++ b/src/UniversalDeviceServiceLib/controllers/ScenariosController.hpp @@ -27,7 +27,7 @@ class ScenariosController final : public Controller { void CleanupScenario(Scenario& scenario); - bool ActivateScenario(const Uuid& id); + std::optional ActivateScenario(const Uuid& id); bool Remove(const Uuid& id); diff --git a/src/UniversalDeviceServiceLib/services/ScenariosService.cpp b/src/UniversalDeviceServiceLib/services/ScenariosService.cpp index 564a2158..c4583c9a 100644 --- a/src/UniversalDeviceServiceLib/services/ScenariosService.cpp +++ b/src/UniversalDeviceServiceLib/services/ScenariosService.cpp @@ -1,5 +1,6 @@ #include "ScenariosService.hpp" +#include #include #include @@ -16,6 +17,7 @@ #include "Scenario.hpp" #include "ScenariosController.hpp" #include "Uuid.hpp" +#include "WebsocketsCache.hpp" ScenariosService::ScenariosService(CrowApp& app, ScenariosController& scenariosController, EventsController& eventsController) : _scenariosController(scenariosController), @@ -91,10 +93,17 @@ crow::response ScenariosService::DeleteScenario(const std::string& scenarioId) { crow::response ScenariosService::ActivateScenario(const std::string& scenarioId) { try { - if (!_scenariosController.ActivateScenario(Uuid{ scenarioId })) { + const std::optional scenario = _scenariosController.ActivateScenario(Uuid{ scenarioId }); + if (!scenario.has_value()) { LOG_ERROR_MSG("Failed to activate scenario"); return crow::response(crow::BAD_REQUEST); } + + for (const auto& [deviceId, command] : scenario.value()._commands) { + auto connection = WebsocketsCache::Instance()->GetWebSocketConnection(deviceId); + if (connection) + connection->send_text(static_cast(command).dump()); + } } catch (...) { LOG_ERROR_MSG("Something went wrong in ClientService::ActivateScenario"); return crow::response{ crow::BAD_REQUEST }; diff --git a/src/UniversalDeviceTests/controllers/ScenariosControllerTests.cpp b/src/UniversalDeviceTests/controllers/ScenariosControllerTests.cpp index 7e53b746..e6b675a1 100644 --- a/src/UniversalDeviceTests/controllers/ScenariosControllerTests.cpp +++ b/src/UniversalDeviceTests/controllers/ScenariosControllerTests.cpp @@ -153,12 +153,12 @@ TEST_CASE("Scenario activation test") { checkEventState(eventId1, true, false); checkEventState(eventId2, false, false); - REQUIRE(scenariosController.ActivateScenario(scenarioId1)); + REQUIRE(scenariosController.ActivateScenario(scenarioId1).has_value()); checkEventState(eventId1, false, true); checkEventState(eventId2, true, true); - REQUIRE(scenariosController.ActivateScenario(scenarioId2)); + REQUIRE(scenariosController.ActivateScenario(scenarioId2).has_value()); checkEventState(eventId1, true, true); checkEventState(eventId2, false, true); From fd495de69c5832af09fec0ed595f41b23912b1cc Mon Sep 17 00:00:00 2001 From: Andrew Guz Date: Sun, 1 Mar 2026 15:18:58 +0300 Subject: [PATCH 07/10] Small update --- .../stackWidgets/ScenariosWidget.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/UniversalDeviceFrontendService/stackWidgets/ScenariosWidget.cpp b/src/UniversalDeviceFrontendService/stackWidgets/ScenariosWidget.cpp index f7da9191..27f988a4 100644 --- a/src/UniversalDeviceFrontendService/stackWidgets/ScenariosWidget.cpp +++ b/src/UniversalDeviceFrontendService/stackWidgets/ScenariosWidget.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -94,6 +95,11 @@ ScenariosWidget::ScenariosWidget(IStackHolder* stackHolder, const ApplicationSet _activateEventsGroup = groupsLayout->addWidget(std::make_unique("Активировать:"), 1); _deactivateEventsGroup = groupsLayout->addWidget(std::make_unique("Деактивировать:"), 1); + + WContainerWidget* commandsCanvas = selectionLayout->addWidget(std::make_unique(), 0, AlignmentFlag::Bottom); + commandsCanvas->setOverflow(Overflow::Scroll, Orientation::Vertical); + auto commandsLayout = commandsCanvas->setLayout(std::make_unique()); + commandsLayout->setContentsMargins(0, 0, 0, 0); } void ScenariosWidget::Initialize(const std::string& data) { Refresh(); } From 94cffecfcaafe24c6f0118f50376f17f74700aa5 Mon Sep 17 00:00:00 2001 From: Andrew Guz Date: Sun, 1 Mar 2026 15:34:12 +0300 Subject: [PATCH 08/10] Delete command from scenario when deleting device --- .../controllers/DevicesController.cpp | 10 ++++- .../controllers/DevicesController.hpp | 7 ++- .../controllers/ScenariosController.cpp | 44 ++++++++++++++----- .../controllers/ScenariosController.hpp | 4 ++ .../services/Platform.cpp | 2 +- .../services/Platform.hpp | 2 +- .../controllers/CleanupControllerTests.cpp | 18 ++++++-- .../controllers/DevicesControllerTests.cpp | 6 ++- .../controllers/EventsTests.cpp | 19 +++++--- .../ThermometerValuesController.cpp | 10 ++++- 10 files changed, 93 insertions(+), 29 deletions(-) diff --git a/src/UniversalDeviceServiceLib/controllers/DevicesController.cpp b/src/UniversalDeviceServiceLib/controllers/DevicesController.cpp index 09914258..055de525 100644 --- a/src/UniversalDeviceServiceLib/controllers/DevicesController.cpp +++ b/src/UniversalDeviceServiceLib/controllers/DevicesController.cpp @@ -15,13 +15,18 @@ #include "IQueryExecutor.hpp" #include "Logger.hpp" #include "Marshaling.hpp" +#include "ScenariosController.hpp" #include "SettingsController.hpp" #include "TimeHelper.hpp" -DevicesController::DevicesController(IQueryExecutor* queryExecutor, SettingsController& settingsController, CommandsController& commandsController) : +DevicesController::DevicesController(IQueryExecutor* queryExecutor, + SettingsController& settingsController, + CommandsController& commandsController, + ScenariosController& scenariosController) : Controller(queryExecutor), _settingsController(settingsController), - _commandsController(commandsController) // + _commandsController(commandsController), + _scenariosController(scenariosController) // { FillCache(); } @@ -139,6 +144,7 @@ bool DevicesController::Remove(const Uuid& id) { _settingsController.Remove(id); _commandsController.Remove(id); + _scenariosController.CleanupCommands(id); return true; } diff --git a/src/UniversalDeviceServiceLib/controllers/DevicesController.hpp b/src/UniversalDeviceServiceLib/controllers/DevicesController.hpp index fd1f0e81..6aba1982 100644 --- a/src/UniversalDeviceServiceLib/controllers/DevicesController.hpp +++ b/src/UniversalDeviceServiceLib/controllers/DevicesController.hpp @@ -9,12 +9,16 @@ #include "Controller.hpp" #include "Device.hpp" #include "IQueryExecutor.hpp" +#include "ScenariosController.hpp" #include "SettingsController.hpp" #include "Uuid.hpp" class DevicesController final : public Controller { public: - DevicesController(IQueryExecutor* queryExecutor, SettingsController& settingsController, CommandsController& commandsController); + DevicesController(IQueryExecutor* queryExecutor, + SettingsController& settingsController, + CommandsController& commandsController, + ScenariosController& scenariosController); ~DevicesController() = default; @@ -39,4 +43,5 @@ class DevicesController final : public Controller { mutable Cache _cache; SettingsController& _settingsController; CommandsController& _commandsController; + ScenariosController& _scenariosController; }; diff --git a/src/UniversalDeviceServiceLib/controllers/ScenariosController.cpp b/src/UniversalDeviceServiceLib/controllers/ScenariosController.cpp index c8d4160f..49e67508 100644 --- a/src/UniversalDeviceServiceLib/controllers/ScenariosController.cpp +++ b/src/UniversalDeviceServiceLib/controllers/ScenariosController.cpp @@ -80,17 +80,7 @@ bool ScenariosController::Update(const Scenario& scenario) { if (!_cache.Size()) FillCache(); - const std::string scenarioJson = static_cast(scenario).dump(); - const std::string query = fmt::format("UPDATE Scenarios SET scenario = '{}' WHERE id = '{}'", scenarioJson, scenario._id.data()); - - if (_queryExecutor->Execute(query)) { - _cache.Update(scenario._id, scenario); - return true; - } - - LOG_SQL_ERROR(query); - - return false; + return UpdateImpl(scenario); } void ScenariosController::CleanupScenario(Scenario& scenario) { @@ -174,6 +164,24 @@ bool ScenariosController::Remove(const Uuid& id) { return false; } +bool ScenariosController::CleanupCommands(const Uuid& deviceId) { + std::lock_guard lockGuard{ _mutex }; + + if (!_cache.Size()) + FillCache(); + + bool result = true; + for (Scenario& scenario : _cache.List()) { + const auto iter = scenario._commands.find(deviceId); + if (iter != scenario._commands.end()) { + scenario._commands.erase(iter); + result &= UpdateImpl(scenario); + } + } + + return result; +} + void ScenariosController::FillCache() const { const std::string query = "SELECT * FROM Scenarios"; std::vector> data; @@ -185,3 +193,17 @@ void ScenariosController::FillCache() const { } else LOG_SQL_ERROR(query); } + +bool ScenariosController::UpdateImpl(const Scenario& scenario) { + const std::string scenarioJson = static_cast(scenario).dump(); + const std::string query = fmt::format("UPDATE Scenarios SET scenario = '{}' WHERE id = '{}'", scenarioJson, scenario._id.data()); + + if (_queryExecutor->Execute(query)) { + _cache.Update(scenario._id, scenario); + return true; + } + + LOG_SQL_ERROR(query); + + return false; +} diff --git a/src/UniversalDeviceServiceLib/controllers/ScenariosController.hpp b/src/UniversalDeviceServiceLib/controllers/ScenariosController.hpp index b6c920da..e7f37d55 100644 --- a/src/UniversalDeviceServiceLib/controllers/ScenariosController.hpp +++ b/src/UniversalDeviceServiceLib/controllers/ScenariosController.hpp @@ -31,9 +31,13 @@ class ScenariosController final : public Controller { bool Remove(const Uuid& id); + bool CleanupCommands(const Uuid& deviceId); + private: void FillCache() const; + bool UpdateImpl(const Scenario& scenario); + private: mutable Cache _cache; EventsController& _eventsController; diff --git a/src/UniversalDeviceServiceLib/services/Platform.cpp b/src/UniversalDeviceServiceLib/services/Platform.cpp index d641a33f..0a25ca4d 100644 --- a/src/UniversalDeviceServiceLib/services/Platform.cpp +++ b/src/UniversalDeviceServiceLib/services/Platform.cpp @@ -44,9 +44,9 @@ std::once_flag Platform::s_instanceFlag; Platform::Platform(CrowApp& app, IQueryExecutor* queryExecutor) : _settingsController(queryExecutor), _commandsController(queryExecutor), - _devicesController(queryExecutor, _settingsController, _commandsController), _eventsController(queryExecutor), _scenariosController(queryExecutor, _eventsController, _commandsController), + _devicesController(queryExecutor, _settingsController, _commandsController, _scenariosController), _thermometerValuesController(queryExecutor, _devicesController), _relayValuesController(queryExecutor), _motionRelayValuesController(queryExecutor), diff --git a/src/UniversalDeviceServiceLib/services/Platform.hpp b/src/UniversalDeviceServiceLib/services/Platform.hpp index 4729e994..1c981dee 100644 --- a/src/UniversalDeviceServiceLib/services/Platform.hpp +++ b/src/UniversalDeviceServiceLib/services/Platform.hpp @@ -44,9 +44,9 @@ struct Platform final { SettingsController _settingsController; CommandsController _commandsController; - DevicesController _devicesController; EventsController _eventsController; ScenariosController _scenariosController; + DevicesController _devicesController; ThermometerValuesController _thermometerValuesController; RelayValuesController _relayValuesController; MotionRelayValuesController _motionRelayValuesController; diff --git a/src/UniversalDeviceTests/controllers/CleanupControllerTests.cpp b/src/UniversalDeviceTests/controllers/CleanupControllerTests.cpp index 921897c4..69cd20a7 100644 --- a/src/UniversalDeviceTests/controllers/CleanupControllerTests.cpp +++ b/src/UniversalDeviceTests/controllers/CleanupControllerTests.cpp @@ -8,10 +8,12 @@ #include "CleanupController.hpp" #include "CommandsController.hpp" #include "DevicesController.hpp" +#include "EventsController.hpp" #include "MotionRelayValue.hpp" #include "MotionRelayValuesController.hpp" #include "RelayValue.hpp" #include "RelayValuesController.hpp" +#include "ScenariosController.hpp" #include "SettingsController.hpp" #include "Storage.hpp" #include "ThermometerValue.hpp" @@ -31,7 +33,9 @@ TEST_CASE("CleanupController") { Storage storage{ dbPath }; SettingsController settingsController{ &storage }; CommandsController commandsController{ &storage }; - DevicesController devicesController{ &storage, settingsController, commandsController }; + EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; { ThermometerValuesController thermometerValuesController{ &storage, devicesController }; RelayValuesController relayValuesController{ &storage }; @@ -66,7 +70,9 @@ TEST_CASE("CleanupController") { SettingsController settingsController{ &storage }; CommandsController commandsController{ &storage }; - DevicesController devicesController{ &storage, settingsController, commandsController }; + EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; ThermometerValuesController thermometerValuesController{ &storage, devicesController }; RelayValuesController relayValuesController{ &storage }; MotionRelayValuesController motionRelayValuesController{ &storage }; @@ -95,7 +101,9 @@ TEST_CASE("CleanupController only several") { Storage storage{ dbPath }; SettingsController settingsController{ &storage }; CommandsController commandsController{ &storage }; - DevicesController devicesController{ &storage, settingsController, commandsController }; + EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; constexpr std::size_t count = 50; constexpr std::size_t requestCount = count * 2; { @@ -135,7 +143,9 @@ TEST_CASE("CleanupController only several") { SettingsController settingsController{ &storage }; CommandsController commandsController{ &storage }; - DevicesController devicesController{ &storage, settingsController, commandsController }; + EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; ThermometerValuesController thermometerValuesController{ &storage, devicesController }; RelayValuesController relayValuesController{ &storage }; MotionRelayValuesController motionRelayValuesController{ &storage }; diff --git a/src/UniversalDeviceTests/controllers/DevicesControllerTests.cpp b/src/UniversalDeviceTests/controllers/DevicesControllerTests.cpp index b1a4a152..72e38e72 100644 --- a/src/UniversalDeviceTests/controllers/DevicesControllerTests.cpp +++ b/src/UniversalDeviceTests/controllers/DevicesControllerTests.cpp @@ -12,7 +12,9 @@ #include "Device.hpp" #include "DevicesController.hpp" #include "Enums.hpp" +#include "EventsController.hpp" #include "PeriodSettings.hpp" +#include "ScenariosController.hpp" #include "SettingsController.hpp" #include "Storage.hpp" #include "ThermometerLedBrightness.hpp" @@ -28,7 +30,9 @@ TEST_CASE("DevicesController") { SettingsController settingsController{ &storage }; CommandsController commandsController{ &storage }; - DevicesController devicesController{ &storage, settingsController, commandsController }; + EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; REQUIRE(devicesController.List().size() == 0); REQUIRE(devicesController.Get(Uuid{}).has_value() == false); diff --git a/src/UniversalDeviceTests/controllers/EventsTests.cpp b/src/UniversalDeviceTests/controllers/EventsTests.cpp index 5d8e5da5..a0446d0b 100644 --- a/src/UniversalDeviceTests/controllers/EventsTests.cpp +++ b/src/UniversalDeviceTests/controllers/EventsTests.cpp @@ -25,6 +25,7 @@ #include "RelayEvent.hpp" #include "RelayState.hpp" #include "RelayValue.hpp" +#include "ScenariosController.hpp" #include "SettingsController.hpp" #include "Storage.hpp" #include "SunriseEvent.hpp" @@ -67,8 +68,9 @@ TEST_CASE("TimerEvent") { SettingsController settingsController{ &storage }; CommandsController commandsController{ &storage }; - DevicesController devicesController{ &storage, settingsController, commandsController }; EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; EventsProcessor eventsProcessor{ eventsController, commandsController }; createDevices(devicesController); @@ -168,8 +170,9 @@ TEST_CASE("ThermometerEvent") { SettingsController settingsController{ &storage }; CommandsController commandsController{ &storage }; - DevicesController devicesController{ &storage, settingsController, commandsController }; EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; EventsProcessor eventsProcessor{ eventsController, commandsController }; createDevices(devicesController); @@ -282,8 +285,9 @@ TEST_CASE("RelayEvent") { SettingsController settingsController{ &storage }; CommandsController commandsController{ &storage }; - DevicesController devicesController{ &storage, settingsController, commandsController }; EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; EventsProcessor eventsProcessor{ eventsController, commandsController }; createDevices(devicesController); @@ -393,8 +397,9 @@ TEST_CASE("ThermostatEvent") { SettingsController settingsController{ &storage }; CommandsController commandsController{ &storage }; - DevicesController devicesController{ &storage, settingsController, commandsController }; EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; EventsProcessor eventsProcessor{ eventsController, commandsController }; createDevices(devicesController); @@ -474,8 +479,9 @@ TEST_CASE("SunriseEvent") { SettingsController settingsController{ &storage }; CommandsController commandsController{ &storage }; - DevicesController devicesController{ &storage, settingsController, commandsController }; EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; EventsProcessor eventsProcessor{ eventsController, commandsController }; createDevices(devicesController); @@ -583,8 +589,9 @@ TEST_CASE("SunsetEvent") { SettingsController settingsController{ &storage }; CommandsController commandsController{ &storage }; - DevicesController devicesController{ &storage, settingsController, commandsController }; EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; EventsProcessor eventsProcessor{ eventsController, commandsController }; createDevices(devicesController); diff --git a/src/UniversalDeviceTests/controllers/ThermometerValuesController.cpp b/src/UniversalDeviceTests/controllers/ThermometerValuesController.cpp index daeae481..e2faad80 100644 --- a/src/UniversalDeviceTests/controllers/ThermometerValuesController.cpp +++ b/src/UniversalDeviceTests/controllers/ThermometerValuesController.cpp @@ -13,6 +13,8 @@ #include "Device.hpp" #include "DevicesController.hpp" #include "Enums.hpp" +#include "EventsController.hpp" +#include "ScenariosController.hpp" #include "SettingsController.hpp" #include "Storage.hpp" #include "ThermometerValue.hpp" @@ -34,7 +36,9 @@ TEST_CASE("ThermometerValuesController") { SettingsController settingsController{ &storage }; CommandsController commandsController{ &storage }; - DevicesController devicesController{ &storage, settingsController, commandsController }; + EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; ThermometerValuesController thermometerValuesController{ &storage, devicesController }; Uuid deviceId; @@ -93,7 +97,9 @@ TEST_CASE("ThermometerValuesController multiple add") { SettingsController settingsController{ &storage }; CommandsController commandsController{ &storage }; - DevicesController devicesController{ &storage, settingsController, commandsController }; + EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; ThermometerValuesController thermometerValuesController{ &storage, devicesController }; Uuid deviceId; From 69d5f9969848e92076e8763f95d4f0d653f41522 Mon Sep 17 00:00:00 2001 From: Andrew Guz Date: Sat, 7 Mar 2026 22:07:24 +0300 Subject: [PATCH 09/10] Test for command cleanup --- .../controllers/DevicesControllerTests.cpp | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/src/UniversalDeviceTests/controllers/DevicesControllerTests.cpp b/src/UniversalDeviceTests/controllers/DevicesControllerTests.cpp index 72e38e72..f3d5b5eb 100644 --- a/src/UniversalDeviceTests/controllers/DevicesControllerTests.cpp +++ b/src/UniversalDeviceTests/controllers/DevicesControllerTests.cpp @@ -4,19 +4,26 @@ #include #include #include +#include #include #include #include "CommandsController.hpp" #include "Device.hpp" +#include "DeviceDescription.hpp" #include "DevicesController.hpp" #include "Enums.hpp" +#include "Event.hpp" +#include "EventUtils.hpp" #include "EventsController.hpp" #include "PeriodSettings.hpp" +#include "RelayState.hpp" +#include "Scenario.hpp" #include "ScenariosController.hpp" #include "SettingsController.hpp" #include "Storage.hpp" +#include "ThermometerEvent.hpp" #include "ThermometerLedBrightness.hpp" #include "Uuid.hpp" @@ -111,3 +118,86 @@ TEST_CASE("DevicesController") { std::filesystem::remove(dbPath); } + +TEST_CASE("DevicesController remove from other tables") { + std::filesystem::path dbPath = std::filesystem::temp_directory_path() / "test.db"; + if (std::filesystem::exists(dbPath)) + std::filesystem::remove(dbPath); + + try { + const Uuid device1Id{}; + const Uuid device2Id{}; + const Uuid device3Id{}; + const Uuid eventId{}; + const Uuid scenarioId{}; + + { + Storage storage{ dbPath }; + + SettingsController settingsController{ &storage }; + CommandsController commandsController{ &storage }; + EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; + + Device device1{ + ._id = device1Id, + ._type = DeviceType::Thermometer, + ._timestamp = std::chrono::system_clock::now(), + }; + Device device2{ + ._id = device2Id, + ._type = DeviceType::Relay, + ._timestamp = std::chrono::system_clock::now(), + }; + Device device3{ + ._id = device3Id, + ._type = DeviceType::Relay, + ._timestamp = std::chrono::system_clock::now(), + }; + + REQUIRE(devicesController.Add(device1) == true); + REQUIRE(devicesController.Add(device2) == true); + REQUIRE(devicesController.Add(device3) == true); + + ThermometerEvent thermometerEvent; + thermometerEvent._id = eventId; + thermometerEvent._provider = DeviceDescription{ + ._type = DeviceType::Thermometer, + ._id = device1Id, + }; + thermometerEvent._receiver = DeviceDescription{ + ._type = DeviceType::Relay, + ._id = device2Id, + }; + Event event = std::move(thermometerEvent); + REQUIRE(eventsController.Add(event) == true); + + Scenario scenario{ + ._id = scenarioId, + ._activateEvent = { eventId, }, + ._commands = { { device3Id, RelayState{ ._state = true }} }, + }; + REQUIRE(scenariosController.Add(scenario)); + + REQUIRE(devicesController.Remove(device3Id) == true); + + REQUIRE(scenariosController.Get(scenarioId)->_commands.size() == 0); + } + { + Storage storage{ dbPath }; + + SettingsController settingsController{ &storage }; + CommandsController commandsController{ &storage }; + EventsController eventsController{ &storage }; + ScenariosController scenariosController{ &storage, eventsController, commandsController }; + DevicesController devicesController{ &storage, settingsController, commandsController, scenariosController }; + + REQUIRE(scenariosController.Get(scenarioId)->_commands.size() == 0); + } + } catch (...) { + REQUIRE(false); + } + + std::filesystem::remove(dbPath); +} From a999c409d1c3c2258c53ab4e6396efd652c983a1 Mon Sep 17 00:00:00 2001 From: Andrew Guz Date: Sun, 22 Mar 2026 12:02:57 +0300 Subject: [PATCH 10/10] Update --- .../stackWidgets/ScenariosWidget.cpp | 69 +++++++++++++++++-- .../stackWidgets/ScenariosWidget.hpp | 19 +++++ .../network/RequestHelper.cpp | 5 ++ 3 files changed, 86 insertions(+), 7 deletions(-) diff --git a/src/UniversalDeviceFrontendService/stackWidgets/ScenariosWidget.cpp b/src/UniversalDeviceFrontendService/stackWidgets/ScenariosWidget.cpp index 27f988a4..5f5f6bee 100644 --- a/src/UniversalDeviceFrontendService/stackWidgets/ScenariosWidget.cpp +++ b/src/UniversalDeviceFrontendService/stackWidgets/ScenariosWidget.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -17,14 +18,19 @@ #include #include #include +#include #include #include #include "ApplicationSettings.hpp" #include "BaseStackWidget.hpp" +#include "Command.hpp" #include "Constants.hpp" #include "Defines.hpp" +#include "Device.hpp" +#include "Enums.hpp" #include "Event.hpp" +#include "EventReceiverWidget.hpp" #include "EventUtils.hpp" #include "FrontendDefines.hpp" #include "IStackHolder.hpp" @@ -96,10 +102,10 @@ ScenariosWidget::ScenariosWidget(IStackHolder* stackHolder, const ApplicationSet _deactivateEventsGroup = groupsLayout->addWidget(std::make_unique("Деактивировать:"), 1); - WContainerWidget* commandsCanvas = selectionLayout->addWidget(std::make_unique(), 0, AlignmentFlag::Bottom); - commandsCanvas->setOverflow(Overflow::Scroll, Orientation::Vertical); - auto commandsLayout = commandsCanvas->setLayout(std::make_unique()); - commandsLayout->setContentsMargins(0, 0, 0, 0); + _commandsCanvas = selectionLayout->addWidget(std::make_unique(), 0, AlignmentFlag::Bottom); + _commandsCanvas->setOverflow(Overflow::Auto, Orientation::Vertical); + _commandsCanvas->setOverflow(Overflow::Hidden, Orientation::Horizontal); + _commandsLayout = _commandsCanvas->setLayout(std::make_unique()); } void ScenariosWidget::Initialize(const std::string& data) { Refresh(); } @@ -116,6 +122,7 @@ void ScenariosWidget::Clear() { _deactivateEventsGroup->removeWidget(deactivateCheckBox); _activatedEvents.clear(); _deactivatedEvents.clear(); + CleanupCommandsCanvas(); } void ScenariosWidget::Refresh() { @@ -170,8 +177,10 @@ void ScenariosWidget::AddScenario() { void ScenariosWidget::OnSelection(int index) { const int selectedIndex = _scenariosList->currentIndex(); - if (selectedIndex == -1) + if (selectedIndex == -1) { + CleanupCommandsCanvas(); return; + } const Scenario& scenario = _scenarios.at(selectedIndex); @@ -187,6 +196,7 @@ void ScenariosWidget::OnSelection(int index) { } SetSelectedEventIndexes(_activatedEvents, activatedIndexes); SetSelectedEventIndexes(_deactivatedEvents, deactivatedIndexes); + FillCommandsCanvas(scenario._commands); } void ScenariosWidget::UpdateScenario() { @@ -236,7 +246,7 @@ void ScenariosWidget::DeleteScenario() { Refresh(); } -std::set ScenariosWidget::GetSelectedEventIndexes(const std::vector& container) const { +std::set ScenariosWidget::GetSelectedEventIndexes(const std::vector& container) const { std::set result; for (std::size_t index = 0; index < container.size(); ++index) { if (container.at(index)->isChecked()) @@ -245,12 +255,57 @@ std::set ScenariosWidget::GetSelectedEventIndexes(const std::vector& container, const std::set& indexes) { +void ScenariosWidget::SetSelectedEventIndexes(const std::vector& container, const std::set& indexes) { for (std::size_t index = 0; index < container.size(); ++index) { container.at(index)->setChecked(indexes.contains(index)); } } +void ScenariosWidget::CleanupCommandsCanvas() { + for (auto iter = _commandsWidgets.rbegin(); iter != _commandsWidgets.rend(); ++iter) { + _commandsLayout->removeWidget(*iter); + } + + _commandsWidgets.clear(); + + _commandsLayout->removeWidget(_commandEditor); + _commandsLayout->removeWidget(_addCommandButton); + + _commandEditor = nullptr; + _addCommandButton = nullptr; +} + +Devices ScenariosWidget::GetDevices() { + auto resultJson = RequestHelper::DoGetRequest({ BACKEND_IP, _settings._servicePort, API_CLIENT_DEVICES }, Constants::LoginService); + Devices devices = resultJson.is_null() ? Devices{} : resultJson.get(); + // right now not all devices can receive events + auto newEnd = std::remove_if(devices.begin(), devices.end(), [](const Device& device) { + return device._type == DeviceType::Undefined || device._type == DeviceType::UniversalDevice; + }); + devices.erase(newEnd, devices.end()); + return devices; +} + +void ScenariosWidget::FillCommandsCanvas(const std::unordered_map& commands) { + const Devices devices = GetDevices(); + + _commandEditor = _commandsLayout->addWidget(std::make_unique(), 0, 0, 1, 2); + _commandEditor->SetDevices(devices); + _addCommandButton = _commandsLayout->addWidget(std::make_unique("Добавить"), 0, 2, AlignmentFlag::Right); + WidgetHelper::SetUsualButtonSize(_addCommandButton); + for (const auto& [deviceId, command] : commands) { + EventReceiverWidget* eventReceiverWidget = _commandsLayout->addWidget(std::make_unique(), 0, 0, 1, 2); + eventReceiverWidget->SetDevices(devices); + _commandsWidgets.push_back(eventReceiverWidget); + + WPushButton* deleteButton = _commandsLayout->addWidget(std::make_unique("Добавить"), 0, 2, AlignmentFlag::Right); + WidgetHelper::SetUsualButtonSize(deleteButton); + _commandsWidgets.push_back(deleteButton); + + (void)deviceId; + } +} + bool ScenariosWidget::IsUiValid() const { const auto selectedActivated = GetSelectedEventIndexes(_activatedEvents); const auto selectedDeactivated = GetSelectedEventIndexes(_deactivatedEvents); diff --git a/src/UniversalDeviceFrontendService/stackWidgets/ScenariosWidget.hpp b/src/UniversalDeviceFrontendService/stackWidgets/ScenariosWidget.hpp index 8c11bed3..165263ce 100644 --- a/src/UniversalDeviceFrontendService/stackWidgets/ScenariosWidget.hpp +++ b/src/UniversalDeviceFrontendService/stackWidgets/ScenariosWidget.hpp @@ -3,18 +3,26 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include #include +#include #include #include #include "ApplicationSettings.hpp" #include "BaseStackWidget.hpp" +#include "Command.hpp" +#include "Device.hpp" #include "Event.hpp" +#include "EventReceiverWidget.hpp" #include "IStackHolder.hpp" #include "Scenario.hpp" #include "Uuid.hpp" @@ -44,6 +52,12 @@ class ScenariosWidget final : public Wt::WContainerWidget, public BaseStackWidge void SetSelectedEventIndexes(const std::vector& container, const std::set& indexes); + void CleanupCommandsCanvas(); + + Devices GetDevices(); + + void FillCommandsCanvas(const std::unordered_map& commands); + bool IsUiValid() const; private: @@ -52,6 +66,11 @@ class ScenariosWidget final : public Wt::WContainerWidget, public BaseStackWidge Wt::WLineEdit* _nameEditor; Wt::WGroupBox* _activateEventsGroup; Wt::WGroupBox* _deactivateEventsGroup; + WContainerWidget* _commandsCanvas; + Wt::WGridLayout* _commandsLayout; + EventReceiverWidget* _commandEditor; + Wt::WPushButton* _addCommandButton; + std::vector _commandsWidgets; std::vector _activatedEvents; std::vector _deactivatedEvents; std::vector _scenarios; diff --git a/src/UniversalDeviceLib/network/RequestHelper.cpp b/src/UniversalDeviceLib/network/RequestHelper.cpp index fd7021ae..7d25f7f4 100644 --- a/src/UniversalDeviceLib/network/RequestHelper.cpp +++ b/src/UniversalDeviceLib/network/RequestHelper.cpp @@ -1,17 +1,22 @@ #include "RequestHelper.hpp" +#include +#include #include #include +#include #include #include #include #include #include +#include #include "AccountManager.hpp" #include "Base64Helper.hpp" #include "Logger.hpp" +#include "RequestAddress.hpp" nlohmann::json RequestHelper::DoGetRequest(const RequestAddress& requestAddress, const std::string_view login) { try {