From 9797bc2ed89846ca3fda3d2f57c32eb81ac682b3 Mon Sep 17 00:00:00 2001 From: Vladimir Roncevic Date: Fri, 28 Aug 2026 08:18:46 +0200 Subject: [PATCH] [minihil] Updated minihil modes (toogle, timer, pulse, blink) --- sw/minihil/CMakeLists.txt | 11 +- .../include/core/base_relay_controller.h | 64 ++++++ ...ce_controller.hpp => idevice_controller.h} | 8 + .../core/{irpc_handler.hpp => irpc_handler.h} | 0 .../include/core/{iserver.hpp => iserver.h} | 0 ...ontroller.hpp => gpiod_relay_controller.h} | 13 +- .../network/{tcp_server.hpp => tcp_server.h} | 4 +- .../{jsonrpc_router.hpp => jsonrpc_router.h} | 2 +- sw/minihil/include/sil/sil_relay_controller.h | 17 ++ .../include/sil/sil_relay_controller.hpp | 22 -- sw/minihil/scripts/test_channels.py | 150 ++++++++++++++ sw/minihil/src/core/base_relay_controller.cc | 196 ++++++++++++++++++ ...ntroller.cpp => gpiod_relay_controller.cc} | 24 +-- sw/minihil/src/{main.cpp => main.cc} | 87 +++++++- .../network/{tcp_server.cpp => tcp_server.cc} | 2 +- .../{jsonrpc_router.cpp => jsonrpc_router.cc} | 2 +- sw/minihil/src/sil/sil_relay_controller.cc | 19 ++ sw/minihil/src/sil/sil_relay_controller.cpp | 35 ---- sw/minihildesk/src/app_controller.cc | 31 ++- sw/minihildesk/src/network/tcp_client.h | 2 +- 20 files changed, 581 insertions(+), 108 deletions(-) create mode 100644 sw/minihil/include/core/base_relay_controller.h rename sw/minihil/include/core/{idevice_controller.hpp => idevice_controller.h} (60%) rename sw/minihil/include/core/{irpc_handler.hpp => irpc_handler.h} (100%) rename sw/minihil/include/core/{iserver.hpp => iserver.h} (100%) rename sw/minihil/include/hardware/{gpiod_relay_controller.hpp => gpiod_relay_controller.h} (53%) rename sw/minihil/include/network/{tcp_server.hpp => tcp_server.h} (95%) rename sw/minihil/include/protocol/{jsonrpc_router.hpp => jsonrpc_router.h} (97%) create mode 100644 sw/minihil/include/sil/sil_relay_controller.h delete mode 100644 sw/minihil/include/sil/sil_relay_controller.hpp create mode 100644 sw/minihil/scripts/test_channels.py create mode 100644 sw/minihil/src/core/base_relay_controller.cc rename sw/minihil/src/hardware/{gpiod_relay_controller.cpp => gpiod_relay_controller.cc} (82%) rename sw/minihil/src/{main.cpp => main.cc} (77%) rename sw/minihil/src/network/{tcp_server.cpp => tcp_server.cc} (99%) rename sw/minihil/src/protocol/{jsonrpc_router.cpp => jsonrpc_router.cc} (98%) create mode 100644 sw/minihil/src/sil/sil_relay_controller.cc delete mode 100644 sw/minihil/src/sil/sil_relay_controller.cpp diff --git a/sw/minihil/CMakeLists.txt b/sw/minihil/CMakeLists.txt index 3102a15..c2ca203 100644 --- a/sw/minihil/CMakeLists.txt +++ b/sw/minihil/CMakeLists.txt @@ -18,15 +18,16 @@ include_directories(include) # Source files (explicitly declared) set(SOURCES - src/main.cpp - src/network/tcp_server.cpp - src/protocol/jsonrpc_router.cpp + src/main.cc + src/network/tcp_server.cc + src/protocol/jsonrpc_router.cc + src/core/base_relay_controller.cc ) if(GPIODCXX_FOUND) - list(APPEND SOURCES src/hardware/gpiod_relay_controller.cpp) + list(APPEND SOURCES src/hardware/gpiod_relay_controller.cc) else() - list(APPEND SOURCES src/sil/sil_relay_controller.cpp) + list(APPEND SOURCES src/sil/sil_relay_controller.cc) endif() # Executable diff --git a/sw/minihil/include/core/base_relay_controller.h b/sw/minihil/include/core/base_relay_controller.h new file mode 100644 index 0000000..f53ed68 --- /dev/null +++ b/sw/minihil/include/core/base_relay_controller.h @@ -0,0 +1,64 @@ +#pragma once + +#include "core/idevice_controller.h" +#include +#include +#include +#include +#include +#include + +namespace minihil { + +class BaseRelayController : public IDeviceController { +public: + BaseRelayController(); + ~BaseRelayController() override; + + bool init() override; + bool setRelay(int relayId, bool state) override; + bool getRelay(int relayId) const override; + std::map getAllStates() const override; + + // Replicating microhil_base modes + bool startTimer(int relayId, uint32_t seconds) override; + bool startPulse(int relayId, uint32_t durationMs) override; + bool startBlink(int relayId, uint32_t onMs, uint32_t offMs, uint32_t count) override; + std::string getRelayStatus(int relayId) const override; + +protected: + // Pure virtual methods to be implemented by Gpiod/Sil controllers for actual IO + virtual bool initHardware() = 0; + virtual bool setRelayPhysical(int relayId, bool state) = 0; + +private: + enum class RelayMode { + TOGGLE, + TIMER, + PULSE, + BLINK + }; + + struct RelayState { + RelayMode mode = RelayMode::TOGGLE; + bool active = false; + bool state = false; + std::chrono::steady_clock::time_point startTime; + uint64_t durationMs = 0; + uint32_t blinkOnMs = 0; + uint32_t blinkOffMs = 0; + uint32_t blinkCount = 0; + bool blinkPhase = false; + }; + + void tickLoop(); + void tick(); + void setRelayInternal(int relayId, bool state); + + mutable std::mutex m_mutex; + std::map m_relayStates; + std::thread m_tickThread; + std::atomic m_running{false}; +}; + +} // namespace minihil diff --git a/sw/minihil/include/core/idevice_controller.hpp b/sw/minihil/include/core/idevice_controller.h similarity index 60% rename from sw/minihil/include/core/idevice_controller.hpp rename to sw/minihil/include/core/idevice_controller.h index 0dbae66..90ea0d1 100644 --- a/sw/minihil/include/core/idevice_controller.hpp +++ b/sw/minihil/include/core/idevice_controller.h @@ -1,5 +1,7 @@ #pragma once #include +#include +#include namespace minihil { @@ -18,6 +20,12 @@ class IDeviceController { // Returns a map of all relay channels and their states virtual std::map getAllStates() const = 0; + + // Replicating microhil_base modes + virtual bool startTimer(int relayId, uint32_t seconds) = 0; + virtual bool startPulse(int relayId, uint32_t durationMs) = 0; + virtual bool startBlink(int relayId, uint32_t onMs, uint32_t offMs, uint32_t count) = 0; + virtual std::string getRelayStatus(int relayId) const = 0; }; } // namespace minihil diff --git a/sw/minihil/include/core/irpc_handler.hpp b/sw/minihil/include/core/irpc_handler.h similarity index 100% rename from sw/minihil/include/core/irpc_handler.hpp rename to sw/minihil/include/core/irpc_handler.h diff --git a/sw/minihil/include/core/iserver.hpp b/sw/minihil/include/core/iserver.h similarity index 100% rename from sw/minihil/include/core/iserver.hpp rename to sw/minihil/include/core/iserver.h diff --git a/sw/minihil/include/hardware/gpiod_relay_controller.hpp b/sw/minihil/include/hardware/gpiod_relay_controller.h similarity index 53% rename from sw/minihil/include/hardware/gpiod_relay_controller.hpp rename to sw/minihil/include/hardware/gpiod_relay_controller.h index 6942514..3ef4eee 100644 --- a/sw/minihil/include/hardware/gpiod_relay_controller.hpp +++ b/sw/minihil/include/hardware/gpiod_relay_controller.h @@ -1,6 +1,6 @@ #pragma once -#include "core/idevice_controller.hpp" +#include "core/base_relay_controller.h" #include #include #include @@ -8,22 +8,19 @@ namespace minihil { -class GpiodRelayController : public IDeviceController { +class GpiodRelayController : public BaseRelayController { public: GpiodRelayController(); ~GpiodRelayController() override; - bool init() override; - bool setRelay(int relayId, bool state) override; - bool getRelay(int relayId) const override; - std::map getAllStates() const override; +protected: + bool initHardware() override; + bool setRelayPhysical(int relayId, bool state) override; private: std::string m_chipPath; // Map from Relay ID (1-8) to BCM pin offset std::map m_relayGpioMap; - // Cache for relay states - mutable std::map m_states; struct Impl; std::unique_ptr m_impl; diff --git a/sw/minihil/include/network/tcp_server.hpp b/sw/minihil/include/network/tcp_server.h similarity index 95% rename from sw/minihil/include/network/tcp_server.hpp rename to sw/minihil/include/network/tcp_server.h index 63a3729..a5d86f1 100644 --- a/sw/minihil/include/network/tcp_server.hpp +++ b/sw/minihil/include/network/tcp_server.h @@ -1,7 +1,7 @@ #pragma once -#include "core/iserver.hpp" -#include "core/irpc_handler.hpp" +#include "core/iserver.h" +#include "core/irpc_handler.h" #include #include #include diff --git a/sw/minihil/include/protocol/jsonrpc_router.hpp b/sw/minihil/include/protocol/jsonrpc_router.h similarity index 97% rename from sw/minihil/include/protocol/jsonrpc_router.hpp rename to sw/minihil/include/protocol/jsonrpc_router.h index 8e394f0..4e07461 100644 --- a/sw/minihil/include/protocol/jsonrpc_router.hpp +++ b/sw/minihil/include/protocol/jsonrpc_router.h @@ -1,6 +1,6 @@ #pragma once -#include "core/irpc_handler.hpp" +#include "core/irpc_handler.h" #include #include #include diff --git a/sw/minihil/include/sil/sil_relay_controller.h b/sw/minihil/include/sil/sil_relay_controller.h new file mode 100644 index 0000000..9052c89 --- /dev/null +++ b/sw/minihil/include/sil/sil_relay_controller.h @@ -0,0 +1,17 @@ +#pragma once + +#include "core/base_relay_controller.h" + +namespace minihil { + +class SilRelayController : public BaseRelayController { +public: + SilRelayController(); + ~SilRelayController() override; + +protected: + bool initHardware() override; + bool setRelayPhysical(int relayId, bool state) override; +}; + +} // namespace minihil diff --git a/sw/minihil/include/sil/sil_relay_controller.hpp b/sw/minihil/include/sil/sil_relay_controller.hpp deleted file mode 100644 index fcc64fd..0000000 --- a/sw/minihil/include/sil/sil_relay_controller.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "core/idevice_controller.hpp" -#include - -namespace minihil { - -class SilRelayController : public IDeviceController { -public: - SilRelayController(); - ~SilRelayController() override; - - bool init() override; - bool setRelay(int relayId, bool state) override; - bool getRelay(int relayId) const override; - std::map getAllStates() const override; - -private: - std::map m_states; -}; - -} // namespace minihil diff --git a/sw/minihil/scripts/test_channels.py b/sw/minihil/scripts/test_channels.py new file mode 100644 index 0000000..5a6901a --- /dev/null +++ b/sw/minihil/scripts/test_channels.py @@ -0,0 +1,150 @@ +import os +import subprocess +import socket +import json +import time +import sys + +def send_rpc(sock, method, params=None): + req = { + "jsonrpc": "2.0", + "method": method, + "id": 1 + } + if params is not None: + req["params"] = params + payload = json.dumps(req) + "\n" + sock.sendall(payload.encode('utf-8')) + + # Read response until newline + resp_bytes = b'' + while b'\n' not in resp_bytes: + chunk = sock.recv(1) + if not chunk: + break + resp_bytes += chunk + + resp_str = resp_bytes.decode('utf-8').strip() + if not resp_str: + return None + return json.loads(resp_str) + +def main(): + script_dir = os.path.dirname(os.path.realpath(__file__)) + daemon_path = os.path.abspath(os.path.join(script_dir, "..", "build", "minihild")) + + print(f"Spawning daemon from: {daemon_path}") + proc = subprocess.Popen([daemon_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + + # Wait for startup + time.sleep(1.0) + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + sock.connect(("localhost", 9000)) + print("Connected to minihild.") + + for relay_id in range(1, 9): + print(f"\n--- Testing Relay Channel {relay_id} ---") + + # Test 1: Toggle Mode (set_relay) + print(f"[{relay_id}] Testing set_relay (Toggle mode)...") + res = send_rpc(sock, "set_relay", {"relay_id": relay_id, "state": True}) + assert res["result"]["success"] is True + res = send_rpc(sock, "get_relays") + assert res["result"][str(relay_id)] is True + + res = send_rpc(sock, "get_relay_status", {"relay_id": relay_id}) + assert f"Channel {relay_id}: ON (Toggle)" in res["result"]["status"] + + res = send_rpc(sock, "set_relay", {"relay_id": relay_id, "state": False}) + assert res["result"]["success"] is True + res = send_rpc(sock, "get_relays") + assert res["result"][str(relay_id)] is False + + # Test 2: Timer Mode (start_timer) + print(f"[{relay_id}] Testing start_timer...") + res = send_rpc(sock, "start_timer", {"relay_id": relay_id, "seconds": 2}) + assert res["result"]["success"] is True + + res = send_rpc(sock, "get_relays") + assert res["result"][str(relay_id)] is True + + res = send_rpc(sock, "get_relay_status", {"relay_id": relay_id}) + assert f"Channel {relay_id}: ON (Timer, rem:" in res["result"]["status"] + + print(f"[{relay_id}] Waiting 2.5 seconds for timer to expire...") + time.sleep(2.5) + + res = send_rpc(sock, "get_relays") + assert res["result"][str(relay_id)] is False + + res = send_rpc(sock, "get_relay_status", {"relay_id": relay_id}) + assert f"Channel {relay_id}: OFF (Toggle)" in res["result"]["status"] + + # Test 3: Pulse Mode (start_pulse) + print(f"[{relay_id}] Testing start_pulse...") + res = send_rpc(sock, "start_pulse", {"relay_id": relay_id, "duration_ms": 500}) + assert res["result"]["success"] is True + + res = send_rpc(sock, "get_relays") + assert res["result"][str(relay_id)] is True + + res = send_rpc(sock, "get_relay_status", {"relay_id": relay_id}) + assert f"Channel {relay_id}: ON (Pulse, rem:" in res["result"]["status"] + + print(f"[{relay_id}] Waiting 0.7 seconds for pulse to expire...") + time.sleep(0.7) + + res = send_rpc(sock, "get_relays") + assert res["result"][str(relay_id)] is False + + # Test 4: Blink Mode (start_blink) + print(f"[{relay_id}] Testing start_blink...") + res = send_rpc(sock, "start_blink", {"relay_id": relay_id, "on_ms": 150, "off_ms": 150, "count": 2}) + assert res["result"]["success"] is True + + res = send_rpc(sock, "get_relays") + assert res["result"][str(relay_id)] is True + + res = send_rpc(sock, "get_relay_status", {"relay_id": relay_id}) + assert f"Channel {relay_id}: ON (Blink, count: 2, phase: ON)" in res["result"]["status"] + + print(f"[{relay_id}] Waiting 0.2 seconds (should transition to blink OFF phase)...") + time.sleep(0.2) + res = send_rpc(sock, "get_relays") + assert res["result"][str(relay_id)] is False + res = send_rpc(sock, "get_relay_status", {"relay_id": relay_id}) + assert f"Channel {relay_id}: OFF (Blink, count: 2, phase: OFF)" in res["result"]["status"] + + print(f"[{relay_id}] Waiting 0.2 seconds (should transition to blink ON phase)...") + time.sleep(0.2) + res = send_rpc(sock, "get_relays") + assert res["result"][str(relay_id)] is True + res = send_rpc(sock, "get_relay_status", {"relay_id": relay_id}) + assert f"Channel {relay_id}: ON (Blink, count: 1, phase: ON)" in res["result"]["status"] + + print(f"[{relay_id}] Waiting 0.4 seconds (should finish blink)...") + time.sleep(0.4) + res = send_rpc(sock, "get_relays") + assert res["result"][str(relay_id)] is False + res = send_rpc(sock, "get_relay_status", {"relay_id": relay_id}) + assert f"Channel {relay_id}: OFF (Toggle)" in res["result"]["status"] + + print("\nAll tests passed successfully for all 8 channels!") + + except Exception as e: + print(f"Test failed: {e}") + # Print stdout/stderr of daemon to debug + proc.terminate() + stdout, stderr = proc.communicate() + print(f"Daemon stdout:\n{stdout}") + print(f"Daemon stderr:\n{stderr}") + sys.exit(1) + finally: + sock.close() + proc.terminate() + proc.wait() + +if __name__ == '__main__': + main() diff --git a/sw/minihil/src/core/base_relay_controller.cc b/sw/minihil/src/core/base_relay_controller.cc new file mode 100644 index 0000000..1d2a6c3 --- /dev/null +++ b/sw/minihil/src/core/base_relay_controller.cc @@ -0,0 +1,196 @@ +#include "core/base_relay_controller.h" +#include + +namespace minihil { + +BaseRelayController::BaseRelayController() { + for (int i = 1; i <= 8; ++i) { + m_relayStates[i] = RelayState(); + } +} + +BaseRelayController::~BaseRelayController() { + m_running = false; + if (m_tickThread.joinable()) { + m_tickThread.join(); + } +} + +bool BaseRelayController::init() { + if (!initHardware()) { + return false; + } + m_running = true; + m_tickThread = std::thread(&BaseRelayController::tickLoop, this); + return true; +} + +bool BaseRelayController::setRelay(int relayId, bool state) { + std::lock_guard lock(m_mutex); + if (relayId < 1 || relayId > 8) return false; + + auto& rs = m_relayStates[relayId]; + rs.active = false; + rs.mode = RelayMode::TOGGLE; + setRelayInternal(relayId, state); + return true; +} + +bool BaseRelayController::getRelay(int relayId) const { + std::lock_guard lock(m_mutex); + if (relayId < 1 || relayId > 8) return false; + return m_relayStates.at(relayId).state; +} + +std::map BaseRelayController::getAllStates() const { + std::lock_guard lock(m_mutex); + std::map states; + for (const auto& [relayId, rs] : m_relayStates) { + states[relayId] = rs.state; + } + return states; +} + +bool BaseRelayController::startTimer(int relayId, uint32_t seconds) { + std::lock_guard lock(m_mutex); + if (relayId < 1 || relayId > 8) return false; + + auto& rs = m_relayStates[relayId]; + rs.mode = RelayMode::TIMER; + rs.active = true; + rs.startTime = std::chrono::steady_clock::now(); + rs.durationMs = static_cast(seconds) * 1000; + setRelayInternal(relayId, true); + return true; +} + +bool BaseRelayController::startPulse(int relayId, uint32_t durationMs) { + std::lock_guard lock(m_mutex); + if (relayId < 1 || relayId > 8) return false; + + auto& rs = m_relayStates[relayId]; + rs.mode = RelayMode::PULSE; + rs.active = true; + rs.startTime = std::chrono::steady_clock::now(); + rs.durationMs = durationMs; + setRelayInternal(relayId, true); + return true; +} + +bool BaseRelayController::startBlink(int relayId, uint32_t onMs, uint32_t offMs, uint32_t count) { + std::lock_guard lock(m_mutex); + if (relayId < 1 || relayId > 8) return false; + + auto& rs = m_relayStates[relayId]; + rs.mode = RelayMode::BLINK; + rs.active = true; + rs.blinkOnMs = onMs; + rs.blinkOffMs = offMs; + rs.blinkCount = count; + rs.blinkPhase = true; + rs.startTime = std::chrono::steady_clock::now(); + setRelayInternal(relayId, true); + return true; +} + +std::string BaseRelayController::getRelayStatus(int relayId) const { + std::lock_guard lock(m_mutex); + if (relayId < 1 || relayId > 8) return "invalid channel"; + + const auto& rs = m_relayStates.at(relayId); + const char* phys_state = rs.state ? "ON" : "OFF"; + char buf[256]; + + if (!rs.active) { + snprintf(buf, sizeof(buf), "Channel %d: %s (Toggle)", relayId, phys_state); + } else { + auto now = std::chrono::steady_clock::now(); + switch (rs.mode) { + case RelayMode::TIMER: { + auto elapsed = std::chrono::duration_cast(now - rs.startTime).count(); + uint64_t remaining = (rs.durationMs > static_cast(elapsed)) ? + (rs.durationMs - static_cast(elapsed)) : 0; + snprintf(buf, sizeof(buf), "Channel %d: %s (Timer, rem: %llus)", + relayId, phys_state, (unsigned long long)(remaining / 1000)); + break; + } + case RelayMode::PULSE: { + auto elapsed = std::chrono::duration_cast(now - rs.startTime).count(); + uint64_t remaining = (rs.durationMs > static_cast(elapsed)) ? + (rs.durationMs - static_cast(elapsed)) : 0; + snprintf(buf, sizeof(buf), "Channel %d: %s (Pulse, rem: %llums)", + relayId, phys_state, (unsigned long long)remaining); + break; + } + case RelayMode::BLINK: { + snprintf(buf, sizeof(buf), "Channel %d: %s (Blink, count: %u, phase: %s)", + relayId, phys_state, rs.blinkCount, + rs.blinkPhase ? "ON" : "OFF"); + break; + } + default: + snprintf(buf, sizeof(buf), "Channel %d: %s", relayId, phys_state); + break; + } + } + return std::string(buf); +} + +void BaseRelayController::tickLoop() { + while (m_running) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + tick(); + } +} + +void BaseRelayController::tick() { + std::lock_guard lock(m_mutex); + auto now = std::chrono::steady_clock::now(); + for (auto& [relayId, rs] : m_relayStates) { + if (!rs.active) continue; + + if (rs.mode == RelayMode::TIMER || rs.mode == RelayMode::PULSE) { + auto elapsed = std::chrono::duration_cast(now - rs.startTime).count(); + if (static_cast(elapsed) >= rs.durationMs) { + setRelayInternal(relayId, false); + rs.active = false; + rs.mode = RelayMode::TOGGLE; + std::cout << "" << std::endl; + } + } else if (rs.mode == RelayMode::BLINK) { + auto elapsed = std::chrono::duration_cast(now - rs.startTime).count(); + if (rs.blinkPhase) { + if (static_cast(elapsed) >= rs.blinkOnMs) { + setRelayInternal(relayId, false); + rs.blinkPhase = false; + rs.startTime = now; + } + } else { + if (static_cast(elapsed) >= rs.blinkOffMs) { + if (rs.blinkCount > 0) { + rs.blinkCount--; + if (rs.blinkCount == 0) { + rs.active = false; + rs.mode = RelayMode::TOGGLE; + std::cout << "" << std::endl; + continue; + } + } + setRelayInternal(relayId, true); + rs.blinkPhase = true; + rs.startTime = now; + } + } + } + } +} + +void BaseRelayController::setRelayInternal(int relayId, bool state) { + auto& rs = m_relayStates[relayId]; + if (rs.state != state) { + rs.state = state; + setRelayPhysical(relayId, state); + } +} + +} // namespace minihil diff --git a/sw/minihil/src/hardware/gpiod_relay_controller.cpp b/sw/minihil/src/hardware/gpiod_relay_controller.cc similarity index 82% rename from sw/minihil/src/hardware/gpiod_relay_controller.cpp rename to sw/minihil/src/hardware/gpiod_relay_controller.cc index 04bd05c..18835c3 100644 --- a/sw/minihil/src/hardware/gpiod_relay_controller.cpp +++ b/sw/minihil/src/hardware/gpiod_relay_controller.cc @@ -1,6 +1,6 @@ #ifdef GPIO_HARDWARE_SUPPORT -#include "hardware/gpiod_relay_controller.hpp" +#include "hardware/gpiod_relay_controller.h" #include #include @@ -20,15 +20,11 @@ GpiodRelayController::GpiodRelayController() {1, 5}, {2, 6}, {3, 13}, {4, 16}, {5, 19}, {6, 20}, {7, 21}, {8, 26} }; - - for (int i = 1; i <= 8; ++i) { - m_states[i] = false; - } } GpiodRelayController::~GpiodRelayController() = default; -bool GpiodRelayController::init() { +bool GpiodRelayController::initHardware() { try { std::string selectedChip = m_chipPath; bool chipOpened = false; @@ -76,15 +72,12 @@ bool GpiodRelayController::init() { std::cout << "[GpiodRelayController] GPIO lines requested and configured as OUTPUT." << std::endl; return true; } catch (const std::exception& e) { - std::cerr << "[GpiodRelayController] Exception in init: " << e.what() << std::endl; + std::cerr << "[GpiodRelayController] Exception in initHardware: " << e.what() << std::endl; return false; } } -bool GpiodRelayController::setRelay(int relayId, bool state) { - if (relayId < 1 || relayId > 8) return false; - - m_states[relayId] = state; +bool GpiodRelayController::setRelayPhysical(int relayId, bool state) { try { if (!m_impl->request) return false; unsigned int offset = m_relayGpioMap.at(relayId); @@ -96,15 +89,6 @@ bool GpiodRelayController::setRelay(int relayId, bool state) { } } -bool GpiodRelayController::getRelay(int relayId) const { - if (relayId < 1 || relayId > 8) return false; - return m_states[relayId]; -} - -std::map GpiodRelayController::getAllStates() const { - return m_states; -} - } // namespace minihil #else diff --git a/sw/minihil/src/main.cpp b/sw/minihil/src/main.cc similarity index 77% rename from sw/minihil/src/main.cpp rename to sw/minihil/src/main.cc index d38620a..26b711c 100644 --- a/sw/minihil/src/main.cpp +++ b/sw/minihil/src/main.cc @@ -13,15 +13,15 @@ #include #include -#include "core/idevice_controller.hpp" -#include "protocol/jsonrpc_router.hpp" -#include "network/tcp_server.hpp" +#include "core/idevice_controller.h" +#include "protocol/jsonrpc_router.h" +#include "network/tcp_server.h" #ifdef MOCK_GPIO -#include "sil/sil_relay_controller.hpp" +#include "sil/sil_relay_controller.h" using ConcreteController = minihil::SilRelayController; #else -#include "hardware/gpiod_relay_controller.hpp" +#include "hardware/gpiod_relay_controller.h" using ConcreteController = minihil::GpiodRelayController; #endif @@ -336,6 +336,83 @@ int main(int argc, char* argv[]) { return result; }); + router->registerMethod("start_timer", [controller](const nlohmann::json& params, const nlohmann::json& id) -> nlohmann::json { + if (!params.is_object() || !params.contains("relay_id") || !params.contains("seconds")) { + return {{"code", -32602}, {"error", "Invalid params: 'relay_id' (integer) and 'seconds' (integer) are required."}}; + } + + int relayId = params["relay_id"].get(); + uint32_t seconds = params["seconds"].get(); + + if (relayId < 1 || relayId > 8) { + return {{"code", -32602}, {"error", "Invalid params: 'relay_id' must be between 1 and 8."}}; + } + + bool ok = controller->startTimer(relayId, seconds); + if (!ok) { + return {{"code", -32603}, {"error", "Internal error: Failed to start timer."}}; + } + + return {{"success", true}, {"relay_id", relayId}, {"seconds", seconds}}; + }); + + router->registerMethod("start_pulse", [controller](const nlohmann::json& params, const nlohmann::json& id) -> nlohmann::json { + if (!params.is_object() || !params.contains("relay_id") || !params.contains("duration_ms")) { + return {{"code", -32602}, {"error", "Invalid params: 'relay_id' (integer) and 'duration_ms' (integer) are required."}}; + } + + int relayId = params["relay_id"].get(); + uint32_t durationMs = params["duration_ms"].get(); + + if (relayId < 1 || relayId > 8) { + return {{"code", -32602}, {"error", "Invalid params: 'relay_id' must be between 1 and 8."}}; + } + + bool ok = controller->startPulse(relayId, durationMs); + if (!ok) { + return {{"code", -32603}, {"error", "Internal error: Failed to start pulse."}}; + } + + return {{"success", true}, {"relay_id", relayId}, {"duration_ms", durationMs}}; + }); + + router->registerMethod("start_blink", [controller](const nlohmann::json& params, const nlohmann::json& id) -> nlohmann::json { + if (!params.is_object() || !params.contains("relay_id") || !params.contains("on_ms") || !params.contains("off_ms") || !params.contains("count")) { + return {{"code", -32602}, {"error", "Invalid params: 'relay_id' (integer), 'on_ms' (integer), 'off_ms' (integer), and 'count' (integer) are required."}}; + } + + int relayId = params["relay_id"].get(); + uint32_t onMs = params["on_ms"].get(); + uint32_t offMs = params["off_ms"].get(); + uint32_t count = params["count"].get(); + + if (relayId < 1 || relayId > 8) { + return {{"code", -32602}, {"error", "Invalid params: 'relay_id' must be between 1 and 8."}}; + } + + bool ok = controller->startBlink(relayId, onMs, offMs, count); + if (!ok) { + return {{"code", -32603}, {"error", "Internal error: Failed to start blink."}}; + } + + return {{"success", true}, {"relay_id", relayId}, {"on_ms", onMs}, {"off_ms", offMs}, {"count", count}}; + }); + + router->registerMethod("get_relay_status", [controller](const nlohmann::json& params, const nlohmann::json& id) -> nlohmann::json { + if (!params.is_object() || !params.contains("relay_id")) { + return {{"code", -32602}, {"error", "Invalid params: 'relay_id' (integer) is required."}}; + } + + int relayId = params["relay_id"].get(); + + if (relayId < 1 || relayId > 8) { + return {{"code", -32602}, {"error", "Invalid params: 'relay_id' must be between 1 and 8."}}; + } + + std::string status = controller->getRelayStatus(relayId); + return {{"relay_id", relayId}, {"status", status}}; + }); + // 4. Instantiate Server and Inject Router Dependency auto server = std::make_shared(PORT, router, useSsl, useMtls, caCertPath, certPath, keyPath); diff --git a/sw/minihil/src/network/tcp_server.cpp b/sw/minihil/src/network/tcp_server.cc similarity index 99% rename from sw/minihil/src/network/tcp_server.cpp rename to sw/minihil/src/network/tcp_server.cc index 683122b..1a261ed 100644 --- a/sw/minihil/src/network/tcp_server.cpp +++ b/sw/minihil/src/network/tcp_server.cc @@ -1,4 +1,4 @@ -#include "network/tcp_server.hpp" +#include "network/tcp_server.h" #include #include #include diff --git a/sw/minihil/src/protocol/jsonrpc_router.cpp b/sw/minihil/src/protocol/jsonrpc_router.cc similarity index 98% rename from sw/minihil/src/protocol/jsonrpc_router.cpp rename to sw/minihil/src/protocol/jsonrpc_router.cc index cc2dbf1..40ce597 100644 --- a/sw/minihil/src/protocol/jsonrpc_router.cpp +++ b/sw/minihil/src/protocol/jsonrpc_router.cc @@ -1,4 +1,4 @@ -#include "protocol/jsonrpc_router.hpp" +#include "protocol/jsonrpc_router.h" #include namespace minihil { diff --git a/sw/minihil/src/sil/sil_relay_controller.cc b/sw/minihil/src/sil/sil_relay_controller.cc new file mode 100644 index 0000000..e6789d5 --- /dev/null +++ b/sw/minihil/src/sil/sil_relay_controller.cc @@ -0,0 +1,19 @@ +#include "sil/sil_relay_controller.h" +#include + +namespace minihil { + +SilRelayController::SilRelayController() = default; +SilRelayController::~SilRelayController() = default; + +bool SilRelayController::initHardware() { + std::cout << "[SilRelayController] Software-in-the-Loop simulation initialized." << std::endl; + return true; +} + +bool SilRelayController::setRelayPhysical(int relayId, bool state) { + std::cout << "[SilRelayController] [SIL-SIM] Relay " << relayId << " set to " << (state ? "ON" : "OFF") << std::endl; + return true; +} + +} // namespace minihil diff --git a/sw/minihil/src/sil/sil_relay_controller.cpp b/sw/minihil/src/sil/sil_relay_controller.cpp deleted file mode 100644 index 639fdae..0000000 --- a/sw/minihil/src/sil/sil_relay_controller.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "sil/sil_relay_controller.hpp" -#include - -namespace minihil { - -SilRelayController::SilRelayController() { - for (int i = 1; i <= 8; ++i) { - m_states[i] = false; - } -} - -SilRelayController::~SilRelayController() = default; - -bool SilRelayController::init() { - std::cout << "[SilRelayController] Software-in-the-Loop simulation initialized." << std::endl; - return true; -} - -bool SilRelayController::setRelay(int relayId, bool state) { - if (relayId < 1 || relayId > 8) return false; - m_states[relayId] = state; - std::cout << "[SilRelayController] [SIL-SIM] Relay " << relayId << " set to " << (state ? "ON" : "OFF") << std::endl; - return true; -} - -bool SilRelayController::getRelay(int relayId) const { - if (relayId < 1 || relayId > 8) return false; - return m_states.at(relayId); -} - -std::map SilRelayController::getAllStates() const { - return m_states; -} - -} // namespace minihil diff --git a/sw/minihildesk/src/app_controller.cc b/sw/minihildesk/src/app_controller.cc index f0470d6..b3228fe 100644 --- a/sw/minihildesk/src/app_controller.cc +++ b/sw/minihildesk/src/app_controller.cc @@ -35,9 +35,11 @@ constexpr std::string_view cSysConnected{"[System] Connected successfully."}; constexpr std::string_view cSysConnectFailed{"[System] Connection failed."}; constexpr std::string_view cSysDisconnected{"[System] Disconnected."}; constexpr std::string_view cSysConnectionLost{"[System] Connection lost."}; -constexpr std::string_view cSysErrorNotConnected{"[System] Error: Not connected."}; +constexpr std::string_view cSysErrorNotConnected{ + "[System] Error: Not connected."}; constexpr std::string_view cSysErrorSending{"[System] Error sending command."}; -constexpr std::string_view cSysErrorParsing{"[System] Error parsing response: "}; +constexpr std::string_view cSysErrorParsing{ + "[System] Error parsing response: "}; // JSON keys and values constexpr std::string_view cJsonRpcKey{"jsonrpc"}; @@ -68,7 +70,8 @@ namespace minihildesk { AppController::AppController(std::unique_ptr config, std::unique_ptr client) - : m_config(std::move(config)), m_client(std::move(client)), m_requestId(cInitialRequestId) {} + : m_config(std::move(config)), m_client(std::move(client)), + m_requestId(cInitialRequestId) {} AppController::~AppController() { stop(); } @@ -86,9 +89,12 @@ void AppController::requestConnect(const std::string &ip, int port, bool useSsl, bool useMtls) { requestDisconnect(); - m_signalLog.emit(std::string(cSysConnecting) + ip + cColon.data() + std::to_string(port) + - cSslPrefix.data() + (useSsl ? cOnStr.data() : cOffStr.data()) + - cMtlsPrefix.data() + (useMtls ? cOnStr.data() : cOffStr.data()) + cSuffix.data()); + m_signalLog.emit(std::string(cSysConnecting) + ip + cColon.data() + + std::to_string(port) + cSslPrefix.data() + + (useSsl ? cOnStr.data() : cOffStr.data()) + + cMtlsPrefix.data() + + (useMtls ? cOnStr.data() : cOffStr.data()) + cSuffix.data()); + if (m_client->connect(ip, port, useSsl, useMtls)) { m_config->setIp(ip); m_config->setPort(port); @@ -112,16 +118,20 @@ void AppController::requestConnect(const std::string &ip, int port, bool useSsl, void AppController::requestDisconnect() { bool wasRunning = m_running.exchange(false); + if (m_client) { m_client->shutdownSocket(); // Unblock SSL_read/recv first } + if (m_readThread.joinable()) { m_readThread.join(); } + if (m_client) { m_client ->disconnect(); // Safe to free memory now that read thread is finished } + if (wasRunning) { m_signalConnectionState.emit(false); m_signalLog.emit(cSysDisconnected.data()); @@ -157,6 +167,7 @@ void AppController::sendJsonRpc(const std::string &method, req[cIdKey.data()] = m_requestId++; std::string raw = req.dump() + cNewLine.data(); + if (m_client->send(raw)) { m_signalLog.emit(cTxPrefix.data() + req.dump()); } else { @@ -168,6 +179,7 @@ void AppController::readLoop() { while (m_running) { if (m_client && m_client->isOpen()) { std::string line = m_client->receiveLine(); + if (line.empty()) { if (m_running) { m_running = false; @@ -176,7 +188,9 @@ void AppController::readLoop() { } break; } + processResponse(line); + } else { std::this_thread::sleep_for(std::chrono::milliseconds(cReadLoopSleepMs)); } @@ -195,8 +209,10 @@ void AppController::processResponse(const std::string &rawResponse) { if (res.contains(cResultKey.data())) { auto result = res[cResultKey.data()]; + if (result.is_object()) { - if (result.contains(cRelayIdKey.data()) && result.contains(cStateKey.data())) { + if (result.contains(cRelayIdKey.data()) && + result.contains(cStateKey.data())) { int relayId = result[cRelayIdKey.data()].get(); bool state = result[cStateKey.data()].get(); m_signalRelayState.emit(relayId, state); @@ -213,6 +229,7 @@ void AppController::processResponse(const std::string &rawResponse) { } } } + } catch (const std::exception &e) { m_signalLog.emit(cSysErrorParsing.data() + std::string(e.what())); } diff --git a/sw/minihildesk/src/network/tcp_client.h b/sw/minihildesk/src/network/tcp_client.h index 62758ac..a4b73e7 100644 --- a/sw/minihildesk/src/network/tcp_client.h +++ b/sw/minihildesk/src/network/tcp_client.h @@ -43,7 +43,7 @@ class TcpClient : public ITcpClient { bool isOpen() const override; bool send(const std::string &message) override; - std::string receiveLine() override; // reads until '\n' + std::string receiveLine() override; private: int m_socketFd{-1};