From 4a856759fce450c06c7fc9917dd9b141e4469342 Mon Sep 17 00:00:00 2001 From: sahil agarwal Date: Tue, 20 Jan 2026 07:24:38 +0530 Subject: [PATCH 01/14] -Added windows and linux installation instruction and setup scripts for easy access -Added documentation and local build instruction --- README.md | 257 ++++++++++- docs/API_REFERENCE.md | 682 ++++++++++++++++++++++++++++ docs/BUILDING.md | 246 ++++++++++ docs/GETTING_STARTED.md | 509 +++++++++++++++++++++ setup.sh | 58 +++ setup_windows.bat | 119 +++++ setup_windows_without_cmake.bat | 66 +++ simple_test.cpp | 200 ++++++++ src/Hermes/Hermes.def | 85 ++++ src/Hermes/Makefile.mingw | 25 + test/BoostTestHermes/Makefile.mingw | 21 + 11 files changed, 2263 insertions(+), 5 deletions(-) create mode 100644 docs/API_REFERENCE.md create mode 100644 docs/BUILDING.md create mode 100644 docs/GETTING_STARTED.md create mode 100644 setup.sh create mode 100644 setup_windows.bat create mode 100644 setup_windows_without_cmake.bat create mode 100644 simple_test.cpp create mode 100644 src/Hermes/Hermes.def create mode 100644 src/Hermes/Makefile.mingw create mode 100644 test/BoostTestHermes/Makefile.mingw diff --git a/README.md b/README.md index 3438c14..fb98bbc 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,260 @@ # Hermes "The Hermes Standard for vendor-independent machine-to-machine communication in SMT assembly" is a new, non-proprietary open protocol, based on TCP/IP- and XML, that takes exchange of PCB related data between the different machines in electronics assembly lines to the next level. It was initiated and developed by a group of leading equipment suppliers, bundling their expertise in order to achieve a great step towards advanced process integration. And the story continues: The Hermes Standard Initiative is open to all equipment vendors who want to actively participate in bringing the benefits of Industry 4.0 to their customers. +--- -# Contents +## Quick Start -The GIT-repository contains following directories: +### Prerequisites -- References - storage place for external data and souces the Hermes library depends on (required only with Windows) -- src - full sources of the Hermes library (Windows and Linux) -- test - a test application for the Hermes library (currently only for Windows) +**No Boost.Test library needed for using Hermes!** Only headers required. +| Component | Required For | Notes | +|-----------|--------------|-------| +| **Boost headers** (1.66-1.78) | Building library | Header-only, no compilation | +| **Pugixml** | Building library | Lightweight XML parser | +| **MinGW/GCC** | Compilation | g++ 7.0+ with C++17 support | +| Boost.Test library | Official test suite | **Optional** - for library developers| +### Installation + +**Windows (MinGW):** +```batch +# Clone repository +git clone https://github.com/hermes-org/lib_cpp +cd lib_cpp + +# Run setup (checks dependencies, builds library, runs tests) +setup_windows.bat +``` + +**Linux/macOS:** +```bash +# Clone repository +git clone https://github.com/hermes-org/lib_cpp +cd lib_cpp + +# Run setup +chmod +x setup.sh +./setup.sh +``` + +### Build Output + +- **Windows:** `Hermes.dll` + `Hermes.lib` (import library) +- **Linux:** `libhermes.so.3.1.0` (shared library) +- **Headers:** `src/include/Hermes.h` (main header) + +--- + +## Using Hermes in Your Application + +### 1. Minimal Example + +```cpp +#include +#include + +int main() { + // Create upstream connection (machine → line) + Hermes::Upstream upstream; + + // Configure connection + Hermes::UpstreamSettings settings; + settings._laneId = 1; + settings._hostAddress = "192.168.1.100"; // Downstream machine IP + settings._port = 50101; + + // Connect to line + upstream.Connect(1, settings); + + std::cout << "Connected to Hermes line" << std::endl; + + // Signal board available + Hermes::BoardAvailableData board; + board._boardId = "PCB-12345"; + board._topBarcode = "TOP-BARCODE-001"; + board._lengthInMM = 250.0; + board._widthInMM = 150.0; + + upstream.Signal(1, board); + + // Cleanup + upstream.Disconnect(1); + return 0; +} +``` + +### 2. Compile Your Application + +**Windows:** +```batch +g++ -std=c++17 -I src/include -L . -o my_app.exe my_app.cpp -lHermes -lws2_32 -lmswsock -static-libgcc -static-libstdc++ +``` + +**Linux:** +```bash +g++ -std=c++17 -I src/include -L . -o my_app my_app.cpp -lhermes -lboost_system -lboost_thread -lpugixml -lpthread -Wl,-rpath,'$ORIGIN' +``` + +### 3. Deploy + +Include with your application: +- **Windows:** `my_app.exe` + `Hermes.dll` +- **Linux:** `my_app` + `libhermes.so*` (set `LD_LIBRARY_PATH` or use rpath) + +--- + +## Documentation + +- **[Getting Started Guide](docs/GETTING_STARTED.md)** - Step-by-step tutorial +- **[API Reference](docs/API_REFERENCE.md)** - Complete API documentation +- **[Building from Source](docs/BUILDING.md)** - Advanced build instructions + +--- + +## Architecture + +### Communication Patterns + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ Machine A │────────▶│ Machine B │────────▶│ Machine C │ +│ (Upstream) │ Board │(Up+Down) │ Board │ (Downstream)│ +└─────────────┘ Data └─────────────┘ Data └─────────────┘ + ▲ ▲ ▲ + │ │ │ + └────────────────────────┴────────────────────────┘ + IPC-CFX (Vertical) + MES / ERP / Cloud Systems +``` + +### Message Types + +- **Board handling:** BoardAvailable, BoardForecast, TransportFinished +- **Machine state:** MachineReady, NotificationData, CheckAlive +- **Work order:** GetWorkOrderData, SetWorkOrderData +- **Configuration:** SupervisoryServiceDescription, QueryBoardInfo + +--- + +## Project Structure + +``` +hermes/ +├── src/ +│ ├── Hermes/ # Library source code +│ │ ├── Makefile # Linux/macOS build +│ │ ├── Makefile.mingw # Windows (MinGW) build +│ │ └── *.cpp # Implementation files +│ └── include/ # Public headers +│ └── Hermes.h +├── test/ +│ └── BoostTestHermes/ # Official test suite (optional) +│ ├── Makefile +│ └── Makefile.mingw +├── References/ # Dependencies (not in repo) +│ ├── boost/ # Boost headers +│ └── pugixml/ # Pugixml source +├── docs/ # Documentation +├── setup_windows.bat # Windows build script +├── setup.sh # Linux/macOS build script +└── README.md +``` + +--- + +## Dependencies Setup + +### Boost (Headers Only) + +**Download:** [Boost 1.78.0](https://archives.boost.io/release/1.78.0/source/boost_1_78_0.zip) + +``` +# Extract and copy: +boost_1_78_0/boost/ → References/boost/ +``` + +### Pugixml + +**Download:** [Pugixml Latest](https://github.com/zeux/pugixml/releases) + +``` +# Copy source files: +pugixml-*/src/*.hpp → References/pugixml/ +pugixml-*/src/*.cpp → References/pugixml/ +``` + +--- + +## Testing + +### Simple Test (Recommended) + +```batch +# Creates basic connectivity test +g++ -std=c++17 -I src/include -L . -o simple_test.exe simple_test.cpp -lHermes -lws2_32 -lmswsock +simple_test.exe +``` + +### Official Test Suite (Optional) + +Requires Boost.Test library. See [BUILDING.md](docs/BUILDING.md) for details. + +```batch +cd test/BoostTestHermes +make -f Makefile.mingw +./BoostTestHermes.exe +``` + +--- + +## Compatibility + +### Hermes Standard Versions + +- IPC-HERMES-9852 v1.0 - v1.5 +- SMEMA backward compatibility mode + +### Platforms + +| Platform | Compiler | Status | +|----------|----------|--------| +| Windows 10/11 | MinGW-w64 (GCC 7+) | Tested | +| Windows 10/11 | MSVC 2019+ | ⚠️ Community contributions welcome | +| Linux (Ubuntu/Debian) | GCC 7+ | Tested | +| Linux (Fedora/RHEL) | GCC 7+ | Tested | + +### Boost Version Compatibility + +⚠️ **IMPORTANT:** Use Boost 1.66 - 1.78 only + +- [NO] Boost 1.87+ breaks compatibility (removed `io_service`) +- [OK] Boost 1.78 recommended (stable, well-tested) + +--- + +## IPC Standards Integration + +### Hermes + CFX Together + +The Hermes Standard drives horizontal integration along the SMT line. IPC-CFX complements this by providing a powerful standard for connecting vertically from the SMT line to an MES. + +**Horizontal (Line):** IPC-HERMES-9852 (this library) +**Vertical (MES/ERP):** IPC-2591 (CFX) - separate implementation + +--- + +## Support & Contributing + +- **Issues:** [GitHub Issues](../../issues) +- **Documentation:** [Wiki](../../wiki) +- **Official Standard:** [IPC-HERMES-9852](https://www.the-hermes-standard.info/) + +### Contributing + +Contributions welcome! Please: +1. Follow existing code style +2. Add tests for new features +3. Update documentation +4. Submit pull requests diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md new file mode 100644 index 0000000..6f9cbc0 --- /dev/null +++ b/docs/API_REFERENCE.md @@ -0,0 +1,682 @@ +# Hermes API Reference + +Complete API documentation for the IPC-HERMES-9852 library. + +--- + +## Table of Contents + +- [Core Classes](#core-classes) + - [Upstream](#upstream) + - [Downstream](#downstream) + - [ConfigurationService](#configurationservice) +- [Data Structures](#data-structures) +- [Enumerations](#enumerations) +- [Callbacks](#callbacks) + +--- + +## Core Classes + +### Upstream + +The `Upstream` class manages connections to downstream machines (sending boards forward in the line). + +#### Methods + +##### `void Connect(unsigned laneId, const UpstreamSettings& settings)` + +Establishes connection to a downstream machine. + +**Parameters:** +- `laneId` - Unique identifier for this lane/track (1-based) +- `settings` - Connection configuration + +**Example:** +```cpp +Hermes::Upstream upstream; + +Hermes::UpstreamSettings settings; +settings._laneId = 1; +settings._hostAddress = "192.168.1.20"; +settings._port = 50101; +settings._checkAliveResponseTimeout = 30.0; +settings._reconnectWaitTime = 10.0; + +upstream.Connect(1, settings); +``` + +##### `void Disconnect(unsigned laneId)` + +Closes connection to downstream machine. + +**Parameters:** +- `laneId` - Lane identifier to disconnect + +**Example:** +```cpp +upstream.Disconnect(1); +``` + +##### `void Signal(unsigned laneId, const T& data)` + +Sends a message to the downstream machine. Type `T` can be: +- `BoardAvailableData` - Notify board is ready to transfer +- `BoardForecastData` - Advance notice of upcoming board +- `NotificationData` - Status, warning, or error message +- `MachineReadyData` - Ready to receive next board +- `TransportFinishedData` - Board transfer complete +- `CheckAliveData` - Heartbeat/keepalive + +**Example:** +```cpp +// Signal board available +Hermes::BoardAvailableData board; +board._boardId = "PCB-12345"; +board._topBarcode = "BARCODE-001"; +board._lengthInMM = 250.0; +board._widthInMM = 200.0; +board._thickness = 1.6; +board._conveyorSpeed = 200.0; +board._topClearanceHeight = 15.0; +board._bottomClearanceHeight = 15.0; +board._weight = 0.5; +board._workOrderId = "WO-001"; +board._failedBoard = Hermes::EBoardQuality::eGOOD; + +upstream.Signal(1, board); + +// Signal notification +Hermes::NotificationData notification; +notification._notificationCode = Hermes::ENotificationCode::eMACHINE_READY; +notification._severity = Hermes::ESeverity::eINFO; +notification._description = "Machine initialized successfully"; + +upstream.Signal(1, notification); +``` + +#### Settings Structure + +```cpp +struct UpstreamSettings { + unsigned _laneId; // Lane identifier + std::string _machineId; // This machine's ID + std::string _hostAddress; // Downstream IP address + uint16_t _port; // Downstream port (default: 50101) + double _checkAliveResponseTimeout; // Timeout in seconds (default: 30.0) + double _reconnectWaitTime; // Reconnect delay (default: 10.0) +}; +``` + +--- + +### Downstream + +The `Downstream` class listens for connections from upstream machines (receiving boards). + +#### Methods + +##### `void Enable(unsigned laneId, const DownstreamSettings& settings)` + +Starts listening for upstream machine connections. + +**Parameters:** +- `laneId` - Unique identifier for this lane/track +- `settings` - Listener configuration + +**Example:** +```cpp +Hermes::Downstream downstream; + +Hermes::DownstreamSettings settings; +settings._laneId = 1; +settings._machineId = "MACHINE-02"; +settings._port = 50101; +settings._checkAliveResponseTimeout = 30.0; + +downstream.Enable(1, settings); +``` + +##### `void Disable(unsigned laneId)` + +Stops listening and disconnects any connected upstream machines. + +**Parameters:** +- `laneId` - Lane identifier to disable + +**Example:** +```cpp +downstream.Disable(1); +``` + +##### `void Signal(unsigned laneId, const T& data)` + +Sends a message to the connected upstream machine. Type `T` can be: +- `MachineReadyData` - Ready to accept next board +- `RevokeMachineReadyData` - Cancel ready state +- `StartTransportData` - Begin board transfer +- `StopTransportData` - Stop board movement +- `NotificationData` - Status, warning, or error +- `CheckAliveData` - Heartbeat response + +**Example:** +```cpp +// Signal ready to receive +Hermes::MachineReadyData ready; +ready._boardForecast = Hermes::EBoardForecast::eAVAILABLE; +ready._failedBoard = Hermes::EBoardQuality::eGOOD; + +downstream.Signal(1, ready); +``` + +##### `void RegisterCallback(unsigned laneId, std::function callback)` + +Registers a callback to be invoked when specific message types are received. + +**Callback Types:** +- `BoardAvailableData` - Upstream has board ready +- `BoardForecastData` - Upcoming board notification +- `TransportFinishedData` - Board transfer completed +- `NotificationData` - Upstream status/error +- `CheckAliveData` - Heartbeat received + +**Example:** +```cpp +// Register board arrival handler +downstream.RegisterCallback(1, + [](const Hermes::BoardAvailableData& board) { + std::cout << "Board arrived: " << board._boardId << std::endl; + std::cout << " Size: " << board._lengthInMM << "x" + << board._widthInMM << "mm" << std::endl; + std::cout << " Barcode: " << board._topBarcode << std::endl; + } +); + +// Register notification handler +downstream.RegisterCallback(1, + [](const Hermes::NotificationData& notification) { + std::cout << "Notification: " << notification._description << std::endl; + } +); +``` + +##### `void RegisterConnectionCallbacks(unsigned laneId, std::function onConnect, std::function onDisconnect)` + +Registers callbacks for connection state changes. + +**Example:** +```cpp +downstream.RegisterConnectionCallbacks(1, + []() { + std::cout << "Upstream machine connected" << std::endl; + }, + []() { + std::cout << "Upstream machine disconnected" << std::endl; + } +); +``` + +#### Settings Structure + +```cpp +struct DownstreamSettings { + unsigned _laneId; // Lane identifier + std::string _machineId; // This machine's ID + uint16_t _port; // Listen port (default: 50101) + std::string _optionalClientAddress; // Filter connections from this IP + double _checkAliveResponseTimeout; // Timeout in seconds (default: 30.0) +}; +``` + +--- + +### ConfigurationService + +Manages configuration data exchange and supervisory communication. + +#### Methods + +##### `void Enable(unsigned laneId, const ConfigurationServiceSettings& settings)` + +Starts configuration service listener. + +**Parameters:** +- `laneId` - Lane identifier +- `settings` - Service configuration + +**Example:** +```cpp +Hermes::ConfigurationService config; + +Hermes::ConfigurationServiceSettings settings; +settings._port = 1248; // Default configuration port +settings._machineId = "MACHINE-01"; + +config.Enable(1, settings); +``` + +##### `void Disable(unsigned laneId)` + +Stops configuration service. + +##### `void Signal(unsigned laneId, const T& data)` + +Sends configuration messages. Type `T` can be: +- `ServiceDescriptionData` - Machine capabilities +- `BoardAvailableData` - Board info response +- `SetConfigurationData` - Apply configuration +- `GetConfigurationData` - Request configuration +- `CurrentConfigurationData` - Configuration response + +**Example:** +```cpp +// Send service description +Hermes::ServiceDescriptionData description; +description._machineId = "MACHINE-01"; +description._laneId = 1; +description._supportedFeatures._boardForecast = true; +description._supportedFeatures._queryWorkOrderInfo = true; + +config.Signal(1, description); + +// Request work order data +Hermes::GetWorkOrderDataRequest request; +request._workOrderId = "WO-12345"; + +config.Signal(1, request); +``` + +#### Settings Structure + +```cpp +struct ConfigurationServiceSettings { + std::string _machineId; // Machine identifier + uint16_t _port; // Listen port (default: 1248) + double _checkAliveResponseTimeout; // Timeout in seconds +}; +``` + +--- + +## Data Structures + +### BoardAvailableData + +Describes a PCB board ready for transfer. + +```cpp +struct BoardAvailableData { + std::string _boardId; // Unique board identifier + std::string _boardIdCreatedFrom; // Template/parent board + Hermes::EBoardQuality _failedBoard; // Board quality status + std::string _productTypeId; // Product type + Hermes::EFlipped _flipped; // Board orientation + std::string _topBarcode; // Top side barcode + std::string _bottomBarcode; // Bottom side barcode + double _lengthInMM; // Board length (mm) + double _widthInMM; // Board width (mm) + double _thickness; // Board thickness (mm) + double _conveyorSpeed; // Transport speed (mm/s) + double _topClearanceHeight; // Top clearance (mm) + double _bottomClearanceHeight; // Bottom clearance (mm) + double _weight; // Board weight (kg) + std::string _workOrderId; // Associated work order + std::string _batchId; // Batch identifier +}; +``` + +### BoardForecastData + +Advance notification of an upcoming board. + +```cpp +struct BoardForecastData { + std::string _boardId; // Board identifier + std::string _boardIdCreatedFrom; // Template identifier + double _lengthInMM; // Board length + double _widthInMM; // Board width + double _thickness; // Board thickness + double _conveyorSpeed; // Transport speed + double _topClearanceHeight; // Top clearance + double _bottomClearanceHeight; // Bottom clearance + double _weight; // Board weight + std::string _workOrderId; // Work order + std::string _batchId; // Batch ID + double _timeUntilAvailable; // ETA in seconds +}; +``` + +### MachineReadyData + +Signals downstream machine is ready to accept a board. + +```cpp +struct MachineReadyData { + Hermes::EBoardQuality _failedBoard; // Reject if board quality matches + Hermes::EBoardForecast _boardForecast; // Board forecast capability + std::string _forecastId; // Expected board ID +}; +``` + +### NotificationData + +Status, warning, or error message. + +```cpp +struct NotificationData { + Hermes::ENotificationCode _notificationCode; // Type of notification + Hermes::ESeverity _severity; // Importance level + std::string _description; // Human-readable message +}; +``` + +### TransportFinishedData + +Confirms board transfer completion. + +```cpp +struct TransportFinishedData { + Hermes::ETransferState _transferState; // Success/failure status + std::string _boardId; // Transferred board ID +}; +``` + +--- + +## Enumerations + +### EBoardQuality + +Board quality/status classification. + +```cpp +enum class EBoardQuality { + eUNKNOWN = 0, // Quality not determined + eGOOD = 1, // Board passed all checks + eFAILED = 2 // Board failed quality check +}; +``` + +### EFlipped + +Board orientation. + +```cpp +enum class EFlipped { + eNOT_FLIPPED = 0, // Normal orientation + eFLIPPED = 1 // Board is upside down +}; +``` + +### ETransferState + +Board transfer result. + +```cpp +enum class ETransferState { + eNOT_STARTED = 0, // Transfer not initiated + eINCOMPLETE = 1, // Transfer failed/aborted + eCOMPLETE = 2 // Successfully transferred +}; +``` + +### ENotificationCode + +Predefined notification types. + +```cpp +enum class ENotificationCode { + eUNSPECIFIED = 0, + eMACHINE_READY = 1, + eBOARD_AVAILABLE = 2, + eBOARD_QUALITY_CHANGED = 3, + eCONVEYOR_SPEED_CHANGED = 4, + eMACHINE_PAUSED = 5, + eMACHINE_STOPPED = 6, + eCONNECTION_ERROR = 7, + eBOARD_LOST = 8, + eBOARD_DEFECT = 9 + // ... see Hermes.h for complete list +}; +``` + +### ESeverity + +Notification importance level. + +```cpp +enum class ESeverity { + eFATAL = 1, // Critical error, stop line + eERROR = 2, // Error requiring attention + eWARNING = 3, // Warning, can continue + eINFO = 4 // Informational message +}; +``` + +--- + +## Callbacks + +### Standard Callback Pattern + +All callbacks follow this pattern: + +```cpp +downstream.RegisterCallback(laneId, + [](const MessageType& data) { + // Handle message + } +); +``` + +### Available Callbacks + +#### Downstream Callbacks + +```cpp +// Board available from upstream +RegisterCallback(laneId, callback); + +// Board forecast notification +RegisterCallback(laneId, callback); + +// Transport finished +RegisterCallback(laneId, callback); + +// Notifications from upstream +RegisterCallback(laneId, callback); + +// Connection state +RegisterConnectionCallbacks(laneId, onConnect, onDisconnect); +``` + +#### Upstream Callbacks + +```cpp +// Machine ready signal from downstream +RegisterCallback(laneId, callback); + +// Start/stop transport signals +RegisterCallback(laneId, callback); +RegisterCallback(laneId, callback); + +// Notifications from downstream +RegisterCallback(laneId, callback); + +// Connection state +RegisterConnectionCallbacks(laneId, onConnect, onDisconnect); +``` + +#### Configuration Service Callbacks + +```cpp +// Configuration requests +RegisterCallback(laneId, callback); +RegisterCallback(laneId, callback); + +// Work order queries +RegisterCallback(laneId, callback); +RegisterCallback(laneId, callback); + +// Board info requests +RegisterCallback(laneId, callback); +``` + +--- + +## Complete Example + +```cpp +#include +#include +#include + +class SMTMachine { +private: + Hermes::Upstream upstream_; + Hermes::Downstream downstream_; + +public: + void Initialize() { + SetupDownstream(); + SetupUpstream(); + } + + void SetupDownstream() { + // Configure receiving from previous machine + Hermes::DownstreamSettings settings; + settings._laneId = 1; + settings._machineId = "MACHINE-02"; + settings._port = 50101; + + // Register callbacks BEFORE enabling + downstream_.RegisterCallback(1, + std::bind(&SMTMachine::OnBoardAvailable, this, + std::placeholders::_1) + ); + + downstream_.RegisterCallback(1, + std::bind(&SMTMachine::OnNotification, this, + std::placeholders::_1) + ); + + downstream_.RegisterConnectionCallbacks(1, + []() { std::cout << "Upstream connected" << std::endl; }, + []() { std::cout << "Upstream disconnected" << std::endl; } + ); + + // Start listening + downstream_.Enable(1, settings); + } + + void SetupUpstream() { + // Configure sending to next machine + Hermes::UpstreamSettings settings; + settings._laneId = 1; + settings._machineId = "MACHINE-02"; + settings._hostAddress = "192.168.1.30"; + settings._port = 50101; + + upstream_.Connect(1, settings); + } + + void OnBoardAvailable(const Hermes::BoardAvailableData& board) { + std::cout << "=== Board Arrived ===" << std::endl; + std::cout << "ID: " << board._boardId << std::endl; + std::cout << "Size: " << board._lengthInMM << "x" + << board._widthInMM << "mm" << std::endl; + + // Signal ready to receive + Hermes::MachineReadyData ready; + ready._failedBoard = Hermes::EBoardQuality::eGOOD; + downstream_.Signal(1, ready); + + // Process board + ProcessBoard(board); + + // Forward to next machine + upstream_.Signal(1, board); + + // Confirm transport finished + Hermes::TransportFinishedData finished; + finished._transferState = Hermes::ETransferState::eCOMPLETE; + finished._boardId = board._boardId; + upstream_.Signal(1, finished); + } + + void OnNotification(const Hermes::NotificationData& notification) { + std::cout << "Notification [" << static_cast(notification._severity) + << "]: " << notification._description << std::endl; + } + + void ProcessBoard(const Hermes::BoardAvailableData& board) { + // Simulate processing time + std::cout << "Processing board..." << std::endl; + std::this_thread::sleep_for(std::chrono::seconds(5)); + std::cout << "Processing complete" << std::endl; + } + + void Shutdown() { + downstream_.Disable(1); + upstream_.Disconnect(1); + } +}; + +int main() { + SMTMachine machine; + machine.Initialize(); + + // Run for 1 hour + std::this_thread::sleep_for(std::chrono::hours(1)); + + machine.Shutdown(); + return 0; +} +``` + +--- + +## Error Handling + +The library uses exceptions for error conditions: + +```cpp +try { + upstream.Connect(1, settings); +} catch (const std::exception& e) { + std::cerr << "Connection failed: " << e.what() << std::endl; +} +``` + +Common exceptions: +- Network connection failures +- Invalid configuration +- Protocol violations +- Timeout errors + +--- + +## Thread Safety + +- Each lane operates independently and is thread-safe +- Callbacks are invoked on internal thread - keep handlers quick +- For long operations, dispatch to worker thread from callback + +```cpp +downstream.RegisterCallback(1, + [this](const BoardAvailableData& board) { + // Quick handling in callback + std::thread([this, board]() { + // Long operation in separate thread + ProcessBoard(board); + }).detach(); + } +); +``` + +--- + +**See also:** +- [Getting Started Guide](GETTING_STARTED.md) +- [Examples](EXAMPLES.md) +- [IPC-HERMES-9852 Standard](https://www.the-hermes-standard.info/) \ No newline at end of file diff --git a/docs/BUILDING.md b/docs/BUILDING.md new file mode 100644 index 0000000..668bd23 --- /dev/null +++ b/docs/BUILDING.md @@ -0,0 +1,246 @@ +# Building Hermes C++ Library + +Comprehensive build instructions for all supported platforms. + +## Table of Contents + +- [System Requirements](#system-requirements) +- [Dependencies](#dependencies) +- [Windows Build](#windows-build) +- [MinGW/MSYS2](#mingwmsys2) +- [Visual Studio](#visual-studio) +- [Linux Build](#linux-build) +- [Test Suite](#test-suite) +- [Troubleshooting](#troubleshooting) + +## System Requirements + +- **C++17 compatible compiler** +- **500 MB disk space** for source + dependencies + +## Dependencies + +### Required Dependencies + +#### Boost (1.66-1.78) + +**⚠️ IMPORTANT**: Hermes uses Boost ASIO with `io_service`, which was removed in Boost 1.87+. You MUST use Boost 1.66-1.78. + +**Windows (MinGW):** +```batch +# Download Boost 1.78: +https://archives.boost.io/release/1.78.0/source/boost_1_78_0.zip + +# Extract and copy boost/ folder to: +References\boost\ +``` + +**Linux:** +```bash +# Ubuntu/Debian +sudo apt-get install libboost-dev libboost-system-dev libboost-thread-dev + +# Fedora/RHEL +sudo dnf install boost-devel + +# Arch +sudo pacman -S boost + +``` + +#### Pugixml + +**Windows (MinGW):** +```batch +# Download from: +https://github.com/zeux/pugixml/releases/latest + +# Extract and copy src/*.cpp and src/*.hppto: +References\pugixml\ +``` + +**Linux:** +```bash +# Ubuntu/Debian +sudo apt-get install libpugixml-dev + +# Fedora/RHEL +sudo dnf install pugixml-devel + +# Arch +sudo pacman -S pugixml + +``` + +## Windows Build + +### MinGW/MSYS2 + +#### 1. Install MSYS2 + +Download and install from: https://www.msys2.org/ + +#### 2. Install Build Tools + +Open MSYS2 MinGW 64-bit terminal: +```bash +pacman -Syu +pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-make +``` + +#### 3. Set Up Dependencies + +Follow the [Dependencies](#dependencies) section to install Boost and Pugixml. + +Directory structure should be: +``` +lib_cpp/ +├── References/ +│ ├── boost/ +│ │ ├── asio.hpp +│ │ ├── system/ +│ │ └── ... (all boost headers) +│ └── pugixml/ +│ ├── pugixml.hpp +│ ├── pugiconfig.hpp +│ └── pugixml.cpp +``` + +#### 4. Build +```batch +# Run the build script: +setup_windows.bat + +# Or build manually: +cd src\Hermes +g++ -std=c++17 -O2 -Wall -DHERMES_EXPORTS ^ + -I..\include -I..\..\References -I..\..\References\pugixml ^ + -c *.cpp + +g++ -shared -o Hermes.dll *.o Hermes.def ^ + -lws2_32 -lmswsock -liphlpapi ^ + -static-libgcc -static-libstdc++ +``` + +### Visual Studio + +#### 1. Install Visual Studio + +- Visual Studio +- MSVC v143 C++ Build Tools +- C++ Desktop Development workload + +#### 2. Set Up Dependencies + +Create directory structure: +``` +lib_cpp/ +├── References/ +│ ├── boost/ # Boost headers +│ ├── pugixml/ # Pugixml headers and source +│ ├── lib32/ # 32-bit Boost libraries (optional) +│ └── lib64/ # 64-bit Boost libraries (optional) +``` + +#### 3. Build + +1. Open `Hermes.sln` in Visual Studio +2. Select configuration (Debug/Release) +3. Select platform (Win32/x64) +4. Build Solution (Ctrl+Shift+B) + +Output will be in: +``` +bin\$(Platform)\$(Configuration)\Hermes.dll +``` + + +## Linux Build + +### 1. Install Dependencies + +**Ubuntu/Debian:** +```bash +sudo apt-get update +sudo apt-get install build-essential g++ libboost-all-dev libpugixml-dev +``` + +**Fedora/RHEL:** +```bash +sudo dnf install gcc-c++ boost-devel pugixml-devel +``` + +**Arch:** +```bash +sudo pacman -S base-devel boost pugixml +``` + +### 2. Build +```bash +# Run build script: +chmod +x setup.sh +./setup.sh + +# Or build manually: +cd src/Hermes +g++ -fPIC -std=c++17 -O2 \ + -DBOOST_ERROR_CODE_HEADER_ONLY=0 \ + -DBOOST_SYSTEM_NO_DEPRECATED \ + -I../include \ + -c *.cpp + +g++ -shared -Wl,-soname,libhermes.so.3 \ + -o libhermes.so.3.1.0 *.o \ + -lboost_system -lboost_thread -lpugixml -lpthread + +ln -sf libhermes.so.3.1.0 libhermes.so +``` + +## Test Suite + +**Simple Test:** + +### Simple Test (No Boost.Test Required) + +Create `simple_test.cpp`: +```cpp +#include +#include "Hermes.h" + +int main() { + std::cout << "Testing Hermes library..." << std::endl; + + // Create minimal downstream + HermesDownstreamCallbacks callbacks = {}; + HermesDownstream* p = CreateHermesDownstream(1, &callbacks); + + if (p) { + std::cout << "SUCCESS: Library loaded and functional" << std::endl; + DeleteHermesDownstream(p); + return 0; + } + + std::cerr << "FAILED: Could not create Hermes object" << std::endl; + return 1; +} +`` +no Boost compilation needed. +```batch +# Build and run simple test: +g++ -std=c++17 -I src/include -L . ^ + -o simple_test.exe simple_test.cpp ^ + -lHermes -lws2_32 -lmswsock ^ + -static-libgcc -static-libstdc++ + +simple_test.exe +``` + +You can use the test.cpp for testing every fucntion. + + +**Official Test Suite (Advanced):** +Download pre-compiled Boost libraries from: +https://sourceforge.net/projects/boost/files/boost-binaries/ + +Building Boost from source is not required. +Run Makefile in test\BoostTestHermes diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md new file mode 100644 index 0000000..682b4df --- /dev/null +++ b/docs/GETTING_STARTED.md @@ -0,0 +1,509 @@ +# Getting Started with Hermes + +This guide will walk you through using the Hermes library in your SMT application. + +--- + +## Table of Contents + +1. [Installation](#installation) +2. [First Application](#first-application) +3. [Understanding Hermes Roles](#understanding-hermes-roles) +4. [Common Patterns](#common-patterns) +5. [Troubleshooting](#troubleshooting) + +--- + +## Installation + +### Step 1: Install Dependencies + +**Windows (MinGW):** +```batch +# Download Boost 1.78.0 +# https://archives.boost.io/release/1.78.0/source/boost_1_78_0.zip +# Extract and copy boost/ folder to: References\boost\ + +# Download Pugixml +# https://github.com/zeux/pugixml/releases +# Copy src/*.hpp and src/*.cpp to: References\pugixml\ + +# Verify MinGW installed +where g++ +where mingw32-make +``` + +**Linux (Ubuntu/Debian):** +```bash +# Install from package manager +sudo apt-get update +sudo apt-get install build-essential libboost-all-dev libpugixml-dev + +# Verify installation +g++ --version +``` + +### Step 2: Build Hermes Library + +**Windows:** +```batch +cd hermes +setup_windows.bat +``` + +**Linux:** +```bash +cd hermes +chmod +x setup.sh +./setup.sh +``` + +### Step 3: Verify Installation + +You should see: +``` +[OK] Boost found +[OK] Pugixml found +[BUILD] Compiling Hermes library... +[OK] Hermes.dll created +[TEST] Running tests... +[SUCCESS] All tests passed +``` + +--- + +## First Application + +### Hello Hermes - Minimal Connection Test + +Create `hello_hermes.cpp`: + +```cpp +#include +#include +#include +#include + +int main() { + std::cout << "=== Hermes Connection Test ===" << std::endl; + + // Create an upstream connection object + Hermes::Upstream upstream; + + // Configure connection parameters + Hermes::UpstreamSettings settings; + settings._laneId = 1; // Lane/track ID + settings._hostAddress = "192.168.1.10"; // Downstream machine IP + settings._port = 50101; // Hermes default port + + std::cout << "Connecting to " << settings._hostAddress + << ":" << settings._port << std::endl; + + // Attempt connection + upstream.Connect(1, settings); + + // Wait for connection to establish + std::this_thread::sleep_for(std::chrono::seconds(2)); + + std::cout << "Connection established!" << std::endl; + + // Send machine ready notification + Hermes::NotificationData notification; + notification._notificationCode = Hermes::ENotificationCode::eMACHINE_READY; + notification._severity = Hermes::ESeverity::eINFO; + notification._description = "Machine initialized"; + + upstream.Signal(1, notification); + std::cout << "Sent MACHINE_READY notification" << std::endl; + + // Disconnect gracefully + upstream.Disconnect(1); + std::cout << "Disconnected" << std::endl; + + return 0; +} +``` + +### Compile and Run + +**Windows:** +```batch +g++ -std=c++17 -I src/include -L . -o hello_hermes.exe hello_hermes.cpp ^ + -lHermes -lws2_32 -lmswsock -static-libgcc -static-libstdc++ + +hello_hermes.exe +``` + +**Linux:** +```bash +g++ -std=c++17 -I src/include -L . -o hello_hermes hello_hermes.cpp \ + -lhermes -lboost_system -lboost_thread -lpugixml -lpthread \ + -Wl,-rpath,'$ORIGIN' + +./hello_hermes +``` + +--- + +## Understanding Hermes Roles + +Hermes defines three main communication roles: + +### 1. Upstream (Sending Machine) + +A machine that sends boards **downstream** to the next machine. + +```cpp +Hermes::Upstream upstream; + +// Connect to next machine +UpstreamSettings settings; +settings._hostAddress = "192.168.1.20"; // Next machine IP +settings._port = 50101; + +upstream.Connect(1, settings); + +// Signal board available +BoardAvailableData board; +board._boardId = "PCB-001"; +board._topBarcode = "BARCODE-TOP"; +board._lengthInMM = 250.0; +board._widthInMM = 200.0; + +upstream.Signal(1, board); +``` + +**Use Case:** Loader, printer, placement machine sending to next station + +### 2. Downstream (Receiving Machine) + +A machine that receives boards from the **upstream** machine. + +```cpp +Hermes::Downstream downstream; + +// Listen for incoming boards +DownstreamSettings settings; +settings._port = 50101; // Listen on this port + +downstream.Enable(1, settings); + +// Register callback for board arrival +downstream.RegisterCallback(1, + [](const BoardAvailableData& board) { + std::cout << "Received board: " << board._boardId << std::endl; + } +); +``` + +**Use Case:** Placement machine, reflow oven, AOI receiving from previous station + +### 3. Bidirectional (Middle Machine) + +Most machines in a line are both upstream AND downstream. + +```cpp +// Receive from previous machine +Hermes::Downstream downstream; +downstream.Enable(1, downstreamSettings); + +// Send to next machine +Hermes::Upstream upstream; +upstream.Connect(1, upstreamSettings); + +// Process board and pass it along +downstream.RegisterCallback(1, + [&upstream](const BoardAvailableData& board) { + // Process board here... + + // Send to next machine + upstream.Signal(1, board); + } +); +``` + +**Use Case:** Pick-and-place, inspection, coating machines in middle of line + +--- + +## Common Patterns + +### Pattern 1: Board Flow (Complete Transaction) + +```cpp +#include +#include + +class HermesMachine { +private: + Hermes::Upstream upstream_; + Hermes::Downstream downstream_; + +public: + void Initialize() { + // Setup downstream (receive from previous) + DownstreamSettings downSettings; + downSettings._port = 50101; + downstream_.Enable(1, downSettings); + + // Setup upstream (send to next) + UpstreamSettings upSettings; + upSettings._hostAddress = "192.168.1.20"; + upSettings._port = 50101; + upstream_.Connect(1, upSettings); + + // Register board arrival handler + downstream_.RegisterCallback(1, + std::bind(&HermesMachine::OnBoardArrived, this, + std::placeholders::_1) + ); + } + + void OnBoardArrived(const BoardAvailableData& board) { + std::cout << "Board arrived: " << board._boardId << std::endl; + + // Signal ready to receive + MachineReadyData ready; + ready._failedBoard = Hermes::EBoardQuality::eGOOD; + downstream_.Signal(1, ready); + + // Simulate processing + ProcessBoard(board); + + // Send to next machine + upstream_.Signal(1, board); + + // Signal transport finished + TransportFinishedData finished; + finished._transferState = Hermes::ETransferState::eCOMPLETE; + upstream_.Signal(1, finished); + } + + void ProcessBoard(const BoardAvailableData& board) { + // Your machine logic here + std::cout << "Processing board..." << std::endl; + } +}; + +int main() { + HermesMachine machine; + machine.Initialize(); + + // Keep running + std::this_thread::sleep_for(std::chrono::hours(24)); + return 0; +} +``` + +### Pattern 2: Error Handling and Notifications + +```cpp +void SendErrorNotification(Hermes::Upstream& upstream, + const std::string& error) { + NotificationData notification; + notification._notificationCode = + Hermes::ENotificationCode::eMACHINE_ERROR; + notification._severity = Hermes::ESeverity::eERROR; + notification._description = error; + + upstream.Signal(1, notification); +} + +void HandleBoardDefect(Hermes::Upstream& upstream, + const BoardAvailableData& board) { + // Mark board as failed + board._failedBoard = Hermes::EBoardQuality::eFAILED; + + // Send defect notification + NotificationData notification; + notification._notificationCode = + Hermes::ENotificationCode::eBOARD_DEFECT; + notification._severity = Hermes::ESeverity::eWARNING; + notification._description = "Board quality check failed"; + + upstream.Signal(1, notification); + upstream.Signal(1, board); +} +``` + +### Pattern 3: Work Order Management + +```cpp +void RequestWorkOrder(Hermes::ConfigurationService& config) { + GetWorkOrderDataRequest request; + request._workOrderId = "WO-12345"; + + config.Signal(1, request); +} + +void SetWorkOrder(Hermes::ConfigurationService& config, + const std::string& workOrderId) { + SetWorkOrderDataRequest request; + request._workOrderId = workOrderId; + + WorkOrderData workOrder; + workOrder._workOrderIdentifier = workOrderId; + workOrder._boardIdCreatedFrom = "TEMPLATE-001"; + workOrder._productTypeId = "PRODUCT-ABC"; + + request._workOrder = workOrder; + config.Signal(1, request); +} +``` + +### Pattern 4: Callbacks and Events + +```cpp +class MyMachine { +public: + void SetupCallbacks(Hermes::Downstream& downstream) { + // Board available from upstream + downstream.RegisterBoardAvailableCallback(1, + [this](const BoardAvailableData& data) { + OnBoardAvailable(data); + } + ); + + // Board forecast (advance notice) + downstream.RegisterBoardForecastCallback(1, + [this](const BoardForecastData& data) { + OnBoardForecast(data); + } + ); + + // Connection state changes + downstream.RegisterConnectedCallback(1, + [this]() { + std::cout << "Upstream machine connected" << std::endl; + } + ); + + downstream.RegisterDisconnectedCallback(1, + [this]() { + std::cout << "Upstream machine disconnected" << std::endl; + } + ); + } + +private: + void OnBoardAvailable(const BoardAvailableData& board) { + std::cout << "Board: " << board._boardId + << " (" << board._lengthInMM << "x" + << board._widthInMM << "mm)" << std::endl; + } + + void OnBoardForecast(const BoardForecastData& forecast) { + std::cout << "Forecast: " << forecast._boardId + << " arriving in " << forecast._timeUntilAvailable + << "s" << std::endl; + } +}; +``` + +--- + +## Troubleshooting + +### Connection Issues + +**Problem:** `Connect()` fails or times out + +**Solutions:** +```cpp +// 1. Verify network connectivity +ping 192.168.1.20 + +// 2. Check port is not blocked +telnet 192.168.1.20 50101 + +// 3. Verify other machine is listening +// (downstream must Enable() before upstream Connect()) + +// 4. Check firewall settings +# Windows: Allow Hermes.dll through Windows Firewall +# Linux: sudo ufw allow 50101/tcp +``` + +### Compilation Errors + +**Problem:** `undefined reference to boost::asio::io_service` + +**Solution:** Wrong Boost version! Use 1.66-1.78: +```bash +# Download correct version +wget https://archives.boost.io/release/1.78.0/source/boost_1_78_0.zip +``` + +**Problem:** `cannot find -lHermes` + +**Solution:** Ensure `Hermes.dll` is in current directory or library path: +```batch +# Windows +dir Hermes.dll + +# Linux +ls -l libhermes.so* +export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH +``` + +### Runtime Errors + +**Problem:** Application crashes with `DLL not found` + +**Solution:** Copy `Hermes.dll` to executable directory: +```batch +copy Hermes.dll Release\ +``` + +**Problem:** No data received from upstream + +**Solution:** Verify callback registration happens BEFORE `Enable()`: +```cpp +// WRONG order +downstream.Enable(1, settings); +downstream.RegisterCallback(...); // Too late! + +// CORRECT order +downstream.RegisterCallback(...); +downstream.Enable(1, settings); +``` + +--- + +## Next Steps + +1. **Read API Reference:** [API_REFERENCE.md](API_REFERENCE.md) +2. **Study Examples:** [EXAMPLES.md](EXAMPLES.md) +3. **Review Test Code:** `test/BoostTestHermes/*.cpp` +4. **IPC Standard:** [Download IPC-HERMES-9852](https://www.the-hermes-standard.info/) + +--- + +## Quick Reference Card + +```cpp +// UPSTREAM (Sending boards downstream) +Hermes::Upstream up; +up.Connect(laneId, settings); +up.Signal(laneId, boardData); + +// DOWNSTREAM (Receiving boards from upstream) +Hermes::Downstream down; +down.Enable(laneId, settings); +down.RegisterCallback(laneId, callback); + +// CONFIGURATION SERVICE +Hermes::ConfigurationService config; +config.Enable(laneId, settings); +config.Signal(laneId, request); + +// COMMON DATA STRUCTURES +BoardAvailableData board; +NotificationData notification; +MachineReadyData ready; +TransportFinishedData finished; +``` + +--- + +**Need help?** Check [examples/](../examples/) or open an issue on GitHub. \ No newline at end of file diff --git a/setup.sh b/setup.sh new file mode 100644 index 0000000..27ce709 --- /dev/null +++ b/setup.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +set -e + +echo "========================================" +echo " Hermes Build System (Linux/macOS)" +echo "========================================" +echo "" + +# Check dependencies +check_dep() { + if command -v $1 &>/dev/null || pkg-config --exists $2 2>/dev/null; then + echo " [OK] $1" + return 0 + fi + echo " [ERROR] $1 not found" + return 1 +} + +echo "[1/3] Checking dependencies..." +check_dep g++ gcc || exit 1 +check_dep pkg-config || exit 1 +check_dep boost || echo " [WARN] Boost not via pkg-config, checking headers..." +check_dep pugixml || echo " [WARN] Pugixml not via pkg-config, checking headers..." + +# Build library +echo "" +echo "[2/3] Building Hermes library..." +cd src/Hermes +make clean +make +cp -f .libs/libhermes.so* ../../ +cd ../.. + +# Build and run tests +echo "" +echo "[3/3] Running tests..." + +if [ -f "simple_test.cpp" ]; then + echo " Building simple_test..." + g++ -std=c++17 -I src/include -L . -o simple_test simple_test.cpp \ + -lhermes -lboost_system -lboost_thread -lpugixml -lpthread \ + -Wl,-rpath,'$ORIGIN' + ./simple_test || true +fi + +if [ -d "test/BoostTestHermes" ]; then + echo " Building official test suite..." + cd test/BoostTestHermes + make clean + make + ./BoostTestHermes || true + cd ../.. +fi + +echo "" +echo "========================================" +echo " Build Complete!" +echo "========================================" \ No newline at end of file diff --git a/setup_windows.bat b/setup_windows.bat new file mode 100644 index 0000000..c801402 --- /dev/null +++ b/setup_windows.bat @@ -0,0 +1,119 @@ +@echo off +setlocal enabledelayedexpansion + +echo ======================================== +echo Hermes Build System (Windows/MinGW) +echo ======================================== +echo. + +REM Detect make command +set MAKE_CMD= +for %%m in (mingw32-make make gmake) do ( + where %%m >nul 2>&1 + if !errorlevel! equ 0 ( + set MAKE_CMD=%%m + goto :make_found + ) +) +:make_found + +if "!MAKE_CMD!"=="" ( + echo [ERROR] No make command found (tried mingw32-make, make, gmake^) + echo. + echo Please ensure MinGW is installed and in PATH + echo Download: https://www.mingw-w64.org/ + pause + exit /b 1 +) + +echo [INFO] Using make: !MAKE_CMD! +echo. + +REM Check dependencies +call :check_boost || exit /b 1 +call :check_pugixml || exit /b 1 + +REM Patch if needed +call :patch_senderenvelope + +REM Build library +echo [BUILD] Compiling Hermes library... +cd src\Hermes +!MAKE_CMD! -f Makefile.mingw clean +!MAKE_CMD! -f Makefile.mingw +if !errorlevel! neq 0 ( + echo [FAILED] Library build failed + cd ..\.. + pause + exit /b 1 +) +copy /Y Hermes.dll ..\..\Hermes.dll >nul +cd ..\.. + +REM Build and run tests +call :run_tests + +echo. +echo ======================================== +echo Build Complete! +echo ======================================== +pause +exit /b 0 + +REM ============================================================================ +REM Functions +REM ============================================================================ + +:check_boost +if exist "References\boost\version.hpp" ( + echo [OK] Boost found + exit /b 0 +) +echo [ERROR] Boost not found in References\boost\ +echo Download: https://archives.boost.io/release/1.78.0/source/boost_1_78_0.zip +pause +exit /b 1 + +:check_pugixml +if exist "References\pugixml\pugixml.hpp" ( + echo [OK] Pugixml found + exit /b 0 +) +echo [ERROR] Pugixml not found in References\pugixml\ +echo Download: https://github.com/zeux/pugixml +pause +exit /b 1 + +:patch_senderenvelope +cd src\Hermes +findstr /C:"#ifdef _WIN32" SenderEnvelope.cpp >nul 2>&1 +if !errorlevel! neq 0 ( + echo [PATCH] Fixing SenderEnvelope.cpp... + powershell -Command "$c = Get-Content SenderEnvelope.cpp -Raw; $c = $c -replace 'localtime_r\(&cnow, &local_tm\);', ('#ifdef _WIN32' + [char]10 + ' localtime_s(&local_tm, &cnow);' + [char]10 + '#else' + [char]10 + ' localtime_r(&cnow, &local_tm);' + [char]10 + '#endif'); Set-Content SenderEnvelope.cpp -Value $c -NoNewline" +) +cd ..\.. +exit /b 0 + +:run_tests +if exist "simple_test.cpp" ( + echo [TEST] Building simple_test... + g++ -std=c++17 -I src/include -L . -o simple_test.exe simple_test.cpp -lHermes -lws2_32 -lmswsock -static-libgcc -static-libstdc++ + if !errorlevel! equ 0 ( + simple_test.exe + ) +) + +if exist "References\boost_libs\libboost_unit_test_framework*.a" ( + echo [TEST] Building official test suite... + cd test\BoostTestHermes + !MAKE_CMD! -f Makefile.mingw clean + !MAKE_CMD! -f Makefile.mingw + if !errorlevel! equ 0 ( + copy /Y BoostTestHermes.exe ..\..\BoostTestHermes.exe >nul + cd ..\.. + BoostTestHermes.exe --run_test=* --detect_memory_leaks=0 + ) else ( + cd ..\.. + ) +) +exit /b 0 \ No newline at end of file diff --git a/setup_windows_without_cmake.bat b/setup_windows_without_cmake.bat new file mode 100644 index 0000000..b0d6759 --- /dev/null +++ b/setup_windows_without_cmake.bat @@ -0,0 +1,66 @@ +@echo off +setlocal enabledelayedexpansion + +echo ======================================== +echo Hermes Build (Direct Compilation) +echo ======================================== +echo. + +call :check_boost || exit /b 1 +call :check_pugixml || exit /b 1 +call :patch_senderenvelope + +echo [BUILD] Compiling Hermes library... +cd src\Hermes + +del /Q *.o Hermes.dll 2>nul + +set CXXFLAGS=-std=c++17 -O2 -Wall -Wno-unknown-pragmas -DHERMES_EXPORTS +set INCLUDES=-I. -I../include -I../../References -I../../References/pugixml + +for %%f in (*.cpp) do ( + if not "%%f"=="stdafx.cpp" ( + echo %%f + g++ !CXXFLAGS! !INCLUDES! -c %%f + if !errorlevel! neq 0 exit /b 1 + ) +) + +echo pugixml.cpp +g++ -std=c++17 -O2 -I../../References/pugixml -c ../../References/pugixml/pugixml.cpp +if !errorlevel! neq 0 exit /b 1 + +g++ -shared -o Hermes.dll *.o -lws2_32 -lmswsock -liphlpapi -static-libgcc -static-libstdc++ -Wl,--export-all-symbols +if !errorlevel! neq 0 exit /b 1 + +copy /Y Hermes.dll ..\..\Hermes.dll >nul +cd ..\.. + +call :run_tests +echo [OK] Build complete! +pause +exit /b 0 + +:check_boost +if exist "References\boost\version.hpp" (echo [OK] Boost & exit /b 0) +echo [ERROR] Boost not found +exit /b 1 + +:check_pugixml +if exist "References\pugixml\pugixml.hpp" (echo [OK] Pugixml & exit /b 0) +echo [ERROR] Pugixml not found +exit /b 1 + +:patch_senderenvelope +cd src\Hermes +findstr /C:"#ifdef _WIN32" SenderEnvelope.cpp >nul 2>&1 || ( + powershell -Command "$c=gc SenderEnvelope.cpp -Raw; $c=$c -replace 'localtime_r\(&cnow, &local_tm\);','#ifdef _WIN32`n localtime_s(&local_tm, &cnow);`n#else`n localtime_r(&cnow, &local_tm);`n#endif'; sc SenderEnvelope.cpp $c -NoNewline" +) +cd ..\.. +exit /b 0 + +:run_tests +if exist simple_test.cpp ( + g++ -std=c++17 -I src/include -L . -o simple_test.exe simple_test.cpp -lHermes -lws2_32 -lmswsock -static-libgcc -static-libstdc++ && simple_test.exe +) +exit /b 0 \ No newline at end of file diff --git a/simple_test.cpp b/simple_test.cpp new file mode 100644 index 0000000..bbcfd24 --- /dev/null +++ b/simple_test.cpp @@ -0,0 +1,200 @@ +// Simple Hermes Library Test +// No external dependencies except Hermes itself +#include +#include +#include +#include +#include "Hermes.h" + +#ifdef _WIN32 +#include +#else +#include +#endif + +// Simple event for synchronization +class SimpleEvent { +#ifdef _WIN32 + HANDLE handle; +public: + SimpleEvent() { handle = CreateEvent(NULL, TRUE, FALSE, NULL); } + ~SimpleEvent() { CloseHandle(handle); } + void Set() { SetEvent(handle); } + void Wait() { WaitForSingleObject(handle, INFINITE); } +#else + pthread_mutex_t mutex; + pthread_cond_t cond; + bool signaled; +public: + SimpleEvent() : signaled(false) { + pthread_mutex_init(&mutex, NULL); + pthread_cond_init(&cond, NULL); + } + ~SimpleEvent() { + pthread_mutex_destroy(&mutex); + pthread_cond_destroy(&cond); + } + void Set() { + pthread_mutex_lock(&mutex); + signaled = true; + pthread_cond_signal(&cond); + pthread_mutex_unlock(&mutex); + } + void Wait() { + pthread_mutex_lock(&mutex); + while (!signaled) pthread_cond_wait(&cond, &mutex); + pthread_mutex_unlock(&mutex); + } +#endif +}; + +// Global events for test synchronization +SimpleEvent downstreamConnected; +SimpleEvent upstreamConnected; + +// Callbacks +void OnDownstreamConnected(void*, uint32_t sessionId, EHermesState, const HermesConnectionInfo*) { + std::cout << "[DOWNSTREAM] Connected - Session " << sessionId << std::endl; + downstreamConnected.Set(); +} + +void OnUpstreamConnected(void*, uint32_t sessionId, EHermesState, const HermesConnectionInfo*) { + std::cout << "[UPSTREAM] Connected - Session " << sessionId << std::endl; + upstreamConnected.Set(); +} + +void OnTrace(void*, unsigned sessionId, EHermesTraceType type, HermesStringView trace) { + const char* typeStr = "UNKNOWN"; + switch(type) { + case eHERMES_TRACE_TYPE_DEBUG: typeStr = "DEBUG"; break; + case eHERMES_TRACE_TYPE_INFO: typeStr = "INFO"; break; + case eHERMES_TRACE_TYPE_WARNING: typeStr = "WARN"; break; + case eHERMES_TRACE_TYPE_ERROR: typeStr = "ERROR"; break; + case eHERMES_TRACE_TYPE_SENT: typeStr = "SENT"; break; + case eHERMES_TRACE_TYPE_RECEIVED: typeStr = "RECV"; break; + } + std::cout << "[" << typeStr << "] "; + std::cout.write(trace.m_pData, trace.m_size); + std::cout << std::endl; +} + +#ifdef _WIN32 +DWORD WINAPI RunDownstreamThread(void* param) { + RunHermesDownstream(static_cast(param)); + return 0; +} + +DWORD WINAPI RunUpstreamThread(void* param) { + RunHermesUpstream(static_cast(param)); + return 0; +} +#else +void* RunDownstreamThread(void* param) { + RunHermesDownstream(static_cast(param)); + return nullptr; +} + +void* RunUpstreamThread(void* param) { + RunHermesUpstream(static_cast(param)); + return nullptr; +} +#endif + +int main() { + std::cout << "========================================" << std::endl; + std::cout << " Hermes Library Self-Test" << std::endl; + std::cout << "========================================" << std::endl; + std::cout << std::endl; + + // Create downstream (server) + std::cout << "[1/5] Creating downstream connection..." << std::endl; + HermesDownstreamCallbacks downCallbacks = {}; + downCallbacks.m_connectedCallback = {OnDownstreamConnected, nullptr}; + downCallbacks.m_traceCallback = {OnTrace, nullptr}; + + HermesDownstream* pDown = CreateHermesDownstream(1, &downCallbacks); + + const char* machineId = "TestMachine"; + HermesDownstreamSettings downSettings = {}; + downSettings.m_machineId = {machineId, static_cast(strlen(machineId))}; + downSettings.m_port = 50100; + downSettings.m_checkAlivePeriodInSeconds = 60; + downSettings.m_reconnectWaitTimeInSeconds = 5; + downSettings.m_checkAliveResponseMode = eHERMES_CHECK_ALIVE_RESPONSE_MODE_AUTO; + downSettings.m_checkState = eHERMES_CHECK_STATE_SEND_AND_RECEIVE; + + EnableHermesDownstream(pDown, &downSettings); + +#ifdef _WIN32 + HANDLE downThread = CreateThread(0, 0, RunDownstreamThread, pDown, 0, 0); +#else + pthread_t downThread; + pthread_create(&downThread, NULL, RunDownstreamThread, pDown); +#endif + + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + // Create upstream (client) + std::cout << "[2/5] Creating upstream connection..." << std::endl; + HermesUpstreamCallbacks upCallbacks = {}; + upCallbacks.m_connectedCallback = {OnUpstreamConnected, nullptr}; + upCallbacks.m_traceCallback = {OnTrace, nullptr}; + + HermesUpstream* pUp = CreateHermesUpstream(1, &upCallbacks); + + const char* localhost = "127.0.0.1"; + HermesUpstreamSettings upSettings = {}; + upSettings.m_machineId = {machineId, static_cast(strlen(machineId))}; + upSettings.m_hostAddress = {localhost, static_cast(strlen(localhost))}; + upSettings.m_port = 50100; + upSettings.m_checkAlivePeriodInSeconds = 60; + upSettings.m_reconnectWaitTimeInSeconds = 5; + upSettings.m_checkAliveResponseMode = eHERMES_CHECK_ALIVE_RESPONSE_MODE_AUTO; + upSettings.m_checkState = eHERMES_CHECK_STATE_SEND_AND_RECEIVE; + + EnableHermesUpstream(pUp, &upSettings); + +#ifdef _WIN32 + HANDLE upThread = CreateThread(0, 0, RunUpstreamThread, pUp, 0, 0); +#else + pthread_t upThread; + pthread_create(&upThread, NULL, RunUpstreamThread, pUp); +#endif + + std::cout << "[3/5] Waiting for connections..." << std::endl; + + // Wait for both to connect + downstreamConnected.Wait(); + upstreamConnected.Wait(); + + std::cout << "[4/5] Both connections established!" << std::endl; + std::cout << "[5/5] Cleaning up..." << std::endl; + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + // Cleanup + StopHermesUpstream(pUp); + StopHermesDownstream(pDown); + +#ifdef _WIN32 + WaitForSingleObject(upThread, INFINITE); + WaitForSingleObject(downThread, INFINITE); + CloseHandle(upThread); + CloseHandle(downThread); +#else + pthread_join(upThread, NULL); + pthread_join(downThread, NULL); +#endif + + DeleteHermesUpstream(pUp); + DeleteHermesDownstream(pDown); + + std::cout << std::endl; + std::cout << "========================================" << std::endl; + std::cout << " TEST PASSED!" << std::endl; + std::cout << "========================================" << std::endl; + std::cout << "The Hermes library is working correctly." << std::endl; + std::cout << std::endl; + + return 0; +} \ No newline at end of file diff --git a/src/Hermes/Hermes.def b/src/Hermes/Hermes.def new file mode 100644 index 0000000..5dfa70e --- /dev/null +++ b/src/Hermes/Hermes.def @@ -0,0 +1,85 @@ +LIBRARY Hermes +EXPORTS + CreateHermesDownstream + RunHermesDownstream + PostHermesDownstream + EnableHermesDownstream + SignalHermesDownstreamServiceDescription + SignalHermesBoardAvailable + SignalHermesRevokeBoardAvailable + SignalHermesTransportFinished + SignalHermesBoardForecast + SignalHermesSendBoardInfo + SignalHermesDownstreamNotification + SignalHermesDownstreamCheckAlive + SignalHermesDownstreamCommand + ResetHermesDownstream + DisableHermesDownstream + StopHermesDownstream + DeleteHermesDownstream + SignalHermesDownstreamRawXml + ResetHermesDownstreamRawXml + CreateHermesUpstream + RunHermesUpstream + PostHermesUpstream + EnableHermesUpstream + SignalHermesUpstreamServiceDescription + SignalHermesMachineReady + SignalHermesRevokeMachineReady + SignalHermesStartTransport + SignalHermesStopTransport + SignalHermesQueryBoardInfo + SignalHermesUpstreamNotification + SignalHermesUpstreamCheckAlive + SignalHermesUpstreamCommand + ResetHermesUpstream + DisableHermesUpstream + StopHermesUpstream + DeleteHermesUpstream + SignalHermesUpstreamRawXml + ResetHermesUpstreamRawXml + SetHermesConfiguration + GetHermesConfiguration + CreateHermesConfigurationService + RunHermesConfigurationService + PostHermesConfigurationService + EnableHermesConfigurationService + SignalHermesCurrentConfiguration + SignalHermesConfigurationNotification + DisableHermesConfigurationService + StopHermesConfigurationService + DeleteHermesConfigurationService + CreateHermesVerticalService + RunHermesVerticalService + PostHermesVerticalService + EnableHermesVerticalService + SignalHermesVerticalServiceDescription + SignalHermesQueryWorkOrderInfo + SignalHermesReplyWorkOrderInfo + SignalHermesVerticalServiceNotification + SignalHermesVerticalServiceCheckAlive + SignalHermesVerticalCurrentConfiguration + SignalHermesSendHermesCapabilities + SignalHermesBoardArrived + SignalHermesBoardDeparted + ResetHermesVerticalServiceSession + DisableHermesVerticalService + StopHermesVerticalService + DeleteHermesVerticalService + CreateHermesVerticalClient + RunHermesVerticalClient + PostHermesVerticalClient + EnableHermesVerticalClient + SignalHermesVerticalClientDescription + SignalHermesSendWorkOrderInfo + SignalHermesVerticalGetConfiguration + SignalHermesVerticalSetConfiguration + SignalHermesVerticalQueryHermesCapabilities + SignalHermesVerticalClientNotification + SignalHermesVerticalClientCheckAlive + ResetHermesVerticalClient + SignalHermesVerticalClientRawXml + ResetHermesVerticalClientRawXml + DisableHermesVerticalClient + StopHermesVerticalClient + DeleteHermesVerticalClient \ No newline at end of file diff --git a/src/Hermes/Makefile.mingw b/src/Hermes/Makefile.mingw new file mode 100644 index 0000000..c341734 --- /dev/null +++ b/src/Hermes/Makefile.mingw @@ -0,0 +1,25 @@ +VERSION = 3:1 +DBGFLAGS = -O2 -g0 -DNDEBUG +CXXFLAGS = -std=c++17 -Wall -Wno-unknown-pragmas -fPIC -shared $(DBGFLAGS) -DHERMES_EXPORTS +INCLUDES = -I. -I../include -I../../References -I../../References/pugixml +LIBS = -lws2_32 -lmswsock -liphlpapi -static-libgcc -static-libstdc++ + +SOURCES = $(wildcard *.cpp) +OBJECTS = $(SOURCES:.cpp=.o) ../../References/pugixml/pugixml.o +EXECUTABLE = Hermes.dll + +all: $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) + g++ -shared -o $@ $(OBJECTS) $(LIBS) -Wl,--export-all-symbols + +%.o: %.cpp + g++ $(CXXFLAGS) $(INCLUDES) -c $< -o $@ + +../../References/pugixml/pugixml.o: ../../References/pugixml/pugixml.cpp + g++ -std=c++17 -O2 -Wall -I../../References/pugixml -c $< -o $@ + +clean: + del /Q *.o $(EXECUTABLE) 2>nul + +.PHONY: all clean \ No newline at end of file diff --git a/test/BoostTestHermes/Makefile.mingw b/test/BoostTestHermes/Makefile.mingw new file mode 100644 index 0000000..e2991a8 --- /dev/null +++ b/test/BoostTestHermes/Makefile.mingw @@ -0,0 +1,21 @@ +PROGRAM = BoostTestHermes.exe +INCLUDES = -I. -I../../src/include -I../../References +LIBDIRS = -L../../src/Hermes -L../../References/boost_libs +LIBS = -lHermes -lboost_unit_test_framework-mgw -lws2_32 -lmswsock -static-libgcc -static-libstdc++ + +SOURCES = CApiTest.cpp ConfigurationTest.cpp DownstreamTest.cpp HermesDataTest.cpp SerializerTest.cpp TestMain.cpp Trace.cpp UpstreamTest.cpp +OBJECTS = $(SOURCES:.cpp=.o) +CXXFLAGS = -std=c++17 -O2 -Wall -D_WINDOWS $(INCLUDES) + +all: $(PROGRAM) + +$(PROGRAM): $(OBJECTS) + g++ -o $@ $(OBJECTS) $(LIBDIRS) $(LIBS) + +%.o: %.cpp + g++ $(CXXFLAGS) -c $< -o $@ + +clean: + del /Q *.o $(PROGRAM) 2>nul + +.PHONY: all clean \ No newline at end of file From 4a1d072fbb4dc69f046d018b0ce0332a431662a9 Mon Sep 17 00:00:00 2001 From: Sahil Agarwal Date: Wed, 25 Mar 2026 11:08:07 +0530 Subject: [PATCH 02/14] example files and other adjustments --- .gitignore | 665 +++--------------- CMakeLists.txt | 20 + README.md | 279 ++------ References/pugixml/README.txt | 6 - docs/01_Architecture_Overview.md | 45 ++ docs/02_Building_and_Linking.md | 52 ++ docs/03_Modern_API_Basics.md | 55 ++ docs/04_Downstream_Receiver.md | 0 docs/05_Upstream_Sender.md | 72 ++ docs/06_Core_Message_Types.md | 72 ++ docs/07_Network_and_Topology.md | 67 ++ docs/08_Legacy_Interface_API.md | 93 +++ docs/09_Vertical_MES_Integration.md | 86 +++ docs/API_REFERENCE.md | 682 ------------------- docs/BUILDING.md | 288 ++------ docs/GETTING_STARTED.md | 509 -------------- examples/interactive_sender.cpp | 75 ++ examples/machine_b_pnp.cpp | 68 ++ examples/machine_c_oven.cpp | 41 ++ examples/rpi1_upstream.cpp | 155 +++++ examples/rpi2_downstream.cpp | 157 +++++ setup.sh | 58 -- setup_windows.bat | 119 ---- setup_windows_without_cmake.bat | 66 -- simple_test.cpp | 200 ------ src/Hermes.sln | 47 -- src/Hermes/CMakeLists.txt | 43 ++ src/Hermes/Hermes.vcxproj | 255 ------- src/Hermes/Hermes.vcxproj.filters | 235 ------- src/Hermes/Makefile | 43 -- src/Hermes/Makefile.mingw | 25 - src/Hermes/SenderEnvelope.cpp | 8 +- src/include/Hermes.hpp | 2 +- src/include/HermesModern.hpp | 522 ++++++++++++++ src/include/HermesStringView.h | 1 + test/BoostTestHermes/BoardForecastTest.cpp | 67 -- test/BoostTestHermes/BoostTestHermes.vcxproj | 241 ------- test/BoostTestHermes/CMakeLists.txt | 25 + test/BoostTestHermes/Makefile | 35 - test/BoostTestHermes/Makefile.mingw | 21 - 40 files changed, 1864 insertions(+), 3636 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 References/pugixml/README.txt create mode 100644 docs/01_Architecture_Overview.md create mode 100644 docs/02_Building_and_Linking.md create mode 100644 docs/03_Modern_API_Basics.md create mode 100644 docs/04_Downstream_Receiver.md create mode 100644 docs/05_Upstream_Sender.md create mode 100644 docs/06_Core_Message_Types.md create mode 100644 docs/07_Network_and_Topology.md create mode 100644 docs/08_Legacy_Interface_API.md create mode 100644 docs/09_Vertical_MES_Integration.md delete mode 100644 docs/API_REFERENCE.md delete mode 100644 docs/GETTING_STARTED.md create mode 100644 examples/interactive_sender.cpp create mode 100644 examples/machine_b_pnp.cpp create mode 100644 examples/machine_c_oven.cpp create mode 100644 examples/rpi1_upstream.cpp create mode 100644 examples/rpi2_downstream.cpp delete mode 100644 setup.sh delete mode 100644 setup_windows.bat delete mode 100644 setup_windows_without_cmake.bat delete mode 100644 simple_test.cpp delete mode 100644 src/Hermes.sln create mode 100644 src/Hermes/CMakeLists.txt delete mode 100644 src/Hermes/Hermes.vcxproj delete mode 100644 src/Hermes/Hermes.vcxproj.filters delete mode 100644 src/Hermes/Makefile delete mode 100644 src/Hermes/Makefile.mingw create mode 100644 src/include/HermesModern.hpp delete mode 100644 test/BoostTestHermes/BoardForecastTest.cpp delete mode 100644 test/BoostTestHermes/BoostTestHermes.vcxproj create mode 100644 test/BoostTestHermes/CMakeLists.txt delete mode 100644 test/BoostTestHermes/Makefile delete mode 100644 test/BoostTestHermes/Makefile.mingw diff --git a/.gitignore b/.gitignore index 2697e09..e7d5915 100644 --- a/.gitignore +++ b/.gitignore @@ -1,587 +1,86 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. -## -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# Benchmark Results -BenchmarkDotNet.Artifacts/ - -# .NET Core -project.lock.json -project.fragment.lock.json -artifacts/ -**/Properties/launchSettings.json - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta +# ============================================================================== +# Hermes C++ Library (.gitignore) +# ============================================================================== + +# ------------------------------------------------------------------------------ +# 1. Build Directories (CMake Standard) +# ------------------------------------------------------------------------------ +build/ +build-*/ +out/ +bin/ +lib/ +Testing/ + +# ------------------------------------------------------------------------------ +# 2. CMake Generated Artifacts (Safety Fallbacks) +# ------------------------------------------------------------------------------ +CMakeCache.txt +CMakeFiles/ +CMakeScripts/ +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps/ +CMakeUserPresets.json + +# ------------------------------------------------------------------------------ +# 3. Compiled Object & Binary Files +# ------------------------------------------------------------------------------ +*.slo +*.lo +*.o *.obj +*.gch *.pch +*.so +*.dylib +*.dll +*.lai +*.la +*.a +*.lib +*.exe +*.out +*.app +*.exe + +# ------------------------------------------------------------------------------ +# 4. Visual Studio / MSVC Artifacts (Now generated by CMake) +# ------------------------------------------------------------------------------ +.vs/ +*.sln +*.vcxproj +*.vcxproj.user +*.vcxproj.filters +*.suo +*.user *.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# AxoCover is a Code Coverage Tool -.axoCover/* -!.axoCover/settings.json - -# Visual Studio code coverage results -*.coverage -*.coveragexml - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# Note: Comment the next line if you want to checkin your web deploy settings, -# but database connection strings (with potential passwords) will be unencrypted -*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config -# NuGet v3's project.json files produces more ignorable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt -*.appx - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf -*.ndf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat -node_modules/ - -# Typescript v1 declaration files -typings/ - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -*.vbw - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider +*.idb +*.ilk +*.exp +*.iobj +*.ipdb + +# ------------------------------------------------------------------------------ +# 5. IDE Specific (VS Code, CLion, vim) +# ------------------------------------------------------------------------------ +.vscode/ .idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc - -# Cake - Uncomment if you are using it -# tools/** -# !tools/packages.config - -# Tabs Studio -*.tss - -# Telerik's JustMock configuration file -*.jmconfig - -# BizTalk build output -*.btp.cs -*.btm.cs -*.odx.cs -*.xsd.cs -/References/boost/accumulators -/References/boost/function_types -/References/boost/functional -/References/boost/function -/References/boost/format -/References/boost/flyweight -/References/boost/filesystem -/References/boost/exception -/References/boost/endian -/References/boost/dynamic_bitset -/References/boost/detail -/References/boost/date_time -/References/boost/coroutine2 -/References/boost/coroutine -/References/boost/core -/References/boost/convert -/References/boost/context -/References/boost/container -/References/boost/config -/References/boost/concept_check -/References/boost/concept -/References/boost/compatibility/cpp_c_headers -/References/boost/circular_buffer -/References/boost/chrono -/References/boost/bind -/References/boost/bimap -/References/boost/atomic -/References/boost/assign -/References/boost/asio -/References/boost/archive -/References/boost/align -/References/boost/algorithm -/References/boost/iostreams -/References/boost/io -/References/boost/intrusive -/References/boost/interprocess -/References/boost/integer -/References/boost/icl -/References/boost/heap -/References/boost/graph -/References/boost/gil -/References/boost/geometry -/References/boost/fusion -/References/boost/mpl -/References/boost/mpi -/References/boost/move -/References/boost/math -/References/boost/logic -/References/boost/lockfree -/References/boost/local_function -/References/boost/locale -/References/boost/lexical_cast -/References/boost/lambda -/References/boost/iterator -/References/boost/pool -/References/boost/polygon -/References/boost/phoenix -/References/boost/pending -/References/boost/parameter -/References/boost/optional -/References/boost/numeric -/References/boost/multi_index -/References/boost/multi_array -/References/boost/multiprecision -/References/boost/msm -/References/boost/ratio -/References/boost/range -/References/boost/random -/References/boost/python -/References/boost/ptr_container -/References/boost/proto -/References/boost/property_tree -/References/boost/property_map -/References/boost/program_options -/References/boost/preprocessor -/References/boost/predef -/References/boost/timer -/References/boost/thread -/References/boost/test -/References/boost/system -/References/boost/statechart -/References/boost/spirit -/References/boost/sort -/References/boost/smart_ptr -/References/boost/signals2 -/References/boost/signals -/References/boost/serialization -/References/boost/regex -/References/boost/variant -/References/boost/uuid -/References/boost/utility -/References/boost/unordered -/References/boost/units -/References/boost/type_traits -/References/boost/type_index -/References/boost/type_erasure -/References/boost/typeof -/References/boost/tuple -/References/boost/tti -/References/boost/tr1 -/References/boost/xpressive -/References/boost/weak_ptr.hpp -/References/boost/wave.hpp -/References/boost/wave -/References/boost/vmd -/References/boost/visit_each.hpp -/References/boost/version.hpp -/References/boost/variant.hpp -/References/boost/utility.hpp -/References/boost/unordered_set.hpp -/References/boost/unordered_map.hpp -/References/boost/type_traits.hpp -/References/boost/type_index.hpp -/References/boost/type.hpp -/References/boost/token_iterator.hpp -/References/boost/token_functions.hpp -/References/boost/tokenizer.hpp -/References/boost/timer.hpp -/References/boost/throw_exception.hpp -/References/boost/thread.hpp -/References/boost/swap.hpp -/References/boost/static_assert.hpp -/References/boost/spirit.hpp -/References/boost/smart_ptr.hpp -/References/boost/signals2.hpp -/References/boost/signals.hpp -/References/boost/signal.hpp -/References/boost/shared_ptr.hpp -/References/boost/shared_container_iterator.hpp -/References/boost/shared_array.hpp -/References/boost/scope_exit.hpp -/References/boost/scoped_ptr.hpp -/References/boost/scoped_array.hpp -/References/boost/regex_fwd.hpp -/References/boost/regex.hpp -/References/boost/regex.h -/References/boost/ref.hpp -/References/boost/rational.hpp -/References/boost/ratio.hpp -/References/boost/range.hpp -/References/boost/random.hpp -/References/boost/python.hpp -/References/boost/progress.hpp -/References/boost/program_options.hpp -/References/boost/preprocessor.hpp -/References/boost/predef.h -/References/boost/polymorphic_pointer_cast.hpp -/References/boost/polymorphic_cast.hpp -/References/boost/pointer_to_other.hpp -/References/boost/pointer_cast.hpp -/References/boost/pointee.hpp -/References/boost/phoenix.hpp -/References/boost/parameter.hpp -/References/boost/optional.hpp -/References/boost/operators.hpp -/References/boost/non_type.hpp -/References/boost/none_t.hpp -/References/boost/none.hpp -/References/boost/nondet_random.hpp -/References/boost/noncopyable.hpp -/References/boost/next_prior.hpp -/References/boost/multi_index_container_fwd.hpp -/References/boost/multi_index_container.hpp -/References/boost/multi_array.hpp -/References/boost/mpi.hpp -/References/boost/mem_fn.hpp -/References/boost/memory_order.hpp -/References/boost/math_fwd.hpp -/References/boost/make_unique.hpp -/References/boost/make_shared.hpp -/References/boost/make_default.hpp -/References/boost/local_function.hpp -/References/boost/locale.hpp -/References/boost/limits.hpp -/References/boost/lexical_cast.hpp -/References/boost/last_value.hpp -/References/boost/iterator_adaptors.hpp -/References/boost/iterator.hpp -/References/boost/is_placeholder.hpp -/References/boost/io_fwd.hpp -/References/boost/intrusive_ptr.hpp -/References/boost/integer_traits.hpp -/References/boost/integer_fwd.hpp -/References/boost/integer.hpp -/References/boost/indirect_reference.hpp -/References/boost/implicit_cast.hpp -/References/boost/get_pointer.hpp -/References/boost/geometry.hpp -/References/boost/generator_iterator.hpp -/References/boost/function_output_iterator.hpp -/References/boost/function_equal.hpp -/References/boost/functional.hpp -/References/boost/function.hpp -/References/boost/format.hpp -/References/boost/foreach_fwd.hpp -/References/boost/foreach.hpp -/References/boost/flyweight.hpp -/References/boost/filesystem.hpp -/References/boost/exception_ptr.hpp -/References/boost/enable_shared_from_this.hpp -/References/boost/dynamic_bitset_fwd.hpp -/References/boost/dynamic_bitset.hpp -/References/boost/date_time.hpp -/References/boost/cxx11_char_types.hpp -/References/boost/current_function.hpp -/References/boost/cstdlib.hpp -/References/boost/cstdint.hpp -/References/boost/cstdfloat.hpp -/References/boost/cregex.hpp -/References/boost/crc.hpp -/References/boost/convert.hpp -/References/boost/config.hpp -/References/boost/concept_check.hpp -/References/boost/concept_archetype.hpp -/References/boost/compressed_pair.hpp -/References/boost/circular_buffer_fwd.hpp -/References/boost/circular_buffer.hpp -/References/boost/chrono.hpp -/References/boost/checked_delete.hpp -/References/boost/cerrno.hpp -/References/boost/cast.hpp -/References/boost/call_traits.hpp -/References/boost/blank_fwd.hpp -/References/boost/blank.hpp -/References/boost/bind.hpp -/References/boost/bimap.hpp -/References/boost/atomic.hpp -/References/boost/assign.hpp -/References/boost/assert.hpp -/References/boost/asio.hpp -/References/boost/array.hpp -/References/boost/any.hpp -/References/boost/aligned_storage.hpp -/References/boost/align.hpp -/References/pugixml/pugixml.hpp -/References/pugixml/pugixml.cpp -/References/pugixml/pugiconfig.hpp -/tmp/Hermes_Debug_x86 -/References/lib32 -/References/lib64 -/References/boost/winapi -/References/boost/stacktrace.hpp -/References/boost/stacktrace -/References/boost/qvm -/References/boost/process.hpp -/References/boost/process -/References/boost/poly_collection -/References/boost/operators_v1.hpp -/References/boost/mp11.hpp -/References/boost/mp11 -/References/boost/metaparse.hpp -/References/boost/metaparse -/References/boost/hof.hpp -/References/boost/hof -/References/boost/hana.hpp -/References/boost/hana -/References/boost/fiber -/References/boost/dll.hpp -/References/boost/dll -/References/boost/contract_macro.hpp -/References/boost/contract.hpp -/References/boost/contract -/References/boost/container_hash -/References/boost/compute.hpp -/References/boost/compute -/References/boost/callable_traits.hpp -/References/boost/callable_traits -/References/boost/beast.hpp -/References/boost/beast -/src/include/HermesStringView.hpp.bak -/src/include/HermesDataConversion.hpp.bak -/src/include/HermesData.hpp.bak -/src/include/HermesData.h.bak -/src/include/Hermes.hpp.bak -/src/Hermes/VerticalService.cpp.bak -/src/Hermes/VerticalClientSession.h.bak -/src/Hermes/VerticalClientSession.cpp.bak -/src/Hermes/VerticalClient.cpp.bak -/src/Hermes/SenderEnvelope.cpp.bak -/src/Hermes/MessageSerialization.cpp.bak -/src/Hermes/Downstream.cpp.bak -/src/Hermes/Makefile.bak -/References/boost -/src/Hermes/pugixml -/version -/tmp/Hermes_Debug_x64 -/src/Hermes/pugixml.notUse -/tmp +*.swp +*.swo + +# ------------------------------------------------------------------------------ +# 6. OS Generated Files +# ------------------------------------------------------------------------------ +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +References/boost +References/pugixml \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d35d3cb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,20 @@ +# ============================================================================== +# Root CMakeLists.txt (Universal Cross-Platform) +# ============================================================================== +cmake_minimum_required(VERSION 3.15) +project(HermesLib VERSION 1.5.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# 1. Find Cross-Platform Threading (Resolves to pthread on Linux/Mac, native on Win) +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +# 2. Find Boost HEADERS ONLY (No 'COMPONENTS system thread' required!) +find_package(Boost 1.66 REQUIRED) + +add_subdirectory(src/Hermes) + +# (Optional: Add your tests subdirectory here if you want them to build) +# add_subdirectory(test/BoostTestHermes) \ No newline at end of file diff --git a/README.md b/README.md index fb98bbc..2a90fba 100644 --- a/README.md +++ b/README.md @@ -1,260 +1,93 @@ -# Hermes - -"The Hermes Standard for vendor-independent machine-to-machine communication in SMT assembly" is a new, non-proprietary open protocol, based on TCP/IP- and XML, that takes exchange of PCB related data between the different machines in electronics assembly lines to the next level. It was initiated and developed by a group of leading equipment suppliers, bundling their expertise in order to achieve a great step towards advanced process integration. And the story continues: The Hermes Standard Initiative is open to all equipment vendors who want to actively participate in bringing the benefits of Industry 4.0 to their customers. ---- - -## Quick Start - -### Prerequisites - -**No Boost.Test library needed for using Hermes!** Only headers required. - -| Component | Required For | Notes | -|-----------|--------------|-------| -| **Boost headers** (1.66-1.78) | Building library | Header-only, no compilation | -| **Pugixml** | Building library | Lightweight XML parser | -| **MinGW/GCC** | Compilation | g++ 7.0+ with C++17 support | -| Boost.Test library | Official test suite | **Optional** - for library developers| - -### Installation - -**Windows (MinGW):** -```batch -# Clone repository -git clone https://github.com/hermes-org/lib_cpp -cd lib_cpp - -# Run setup (checks dependencies, builds library, runs tests) -setup_windows.bat -``` - -**Linux/macOS:** -```bash -# Clone repository -git clone https://github.com/hermes-org/lib_cpp -cd lib_cpp - -# Run setup -chmod +x setup.sh -./setup.sh -``` - -### Build Output - -- **Windows:** `Hermes.dll` + `Hermes.lib` (import library) -- **Linux:** `libhermes.so.3.1.0` (shared library) -- **Headers:** `src/include/Hermes.h` (main header) - ---- - -## Using Hermes in Your Application - -### 1. Minimal Example - -```cpp -#include -#include - -int main() { - // Create upstream connection (machine → line) - Hermes::Upstream upstream; - - // Configure connection - Hermes::UpstreamSettings settings; - settings._laneId = 1; - settings._hostAddress = "192.168.1.100"; // Downstream machine IP - settings._port = 50101; - - // Connect to line - upstream.Connect(1, settings); - - std::cout << "Connected to Hermes line" << std::endl; - - // Signal board available - Hermes::BoardAvailableData board; - board._boardId = "PCB-12345"; - board._topBarcode = "TOP-BARCODE-001"; - board._lengthInMM = 250.0; - board._widthInMM = 150.0; - - upstream.Signal(1, board); - - // Cleanup - upstream.Disconnect(1); - return 0; -} -``` - -### 2. Compile Your Application - -**Windows:** -```batch -g++ -std=c++17 -I src/include -L . -o my_app.exe my_app.cpp -lHermes -lws2_32 -lmswsock -static-libgcc -static-libstdc++ -``` - -**Linux:** -```bash -g++ -std=c++17 -I src/include -L . -o my_app my_app.cpp -lhermes -lboost_system -lboost_thread -lpugixml -lpthread -Wl,-rpath,'$ORIGIN' -``` - -### 3. Deploy - -Include with your application: -- **Windows:** `my_app.exe` + `Hermes.dll` -- **Linux:** `my_app` + `libhermes.so*` (set `LD_LIBRARY_PATH` or use rpath) - ---- - -## Documentation - -- **[Getting Started Guide](docs/GETTING_STARTED.md)** - Step-by-step tutorial -- **[API Reference](docs/API_REFERENCE.md)** - Complete API documentation -- **[Building from Source](docs/BUILDING.md)** - Advanced build instructions - ---- - -## Architecture - -### Communication Patterns - -``` -┌─────────────┐ ┌─────────────┐ ┌─────────────┐ -│ Machine A │────────▶│ Machine B │────────▶│ Machine C │ -│ (Upstream) │ Board │(Up+Down) │ Board │ (Downstream)│ -└─────────────┘ Data └─────────────┘ Data └─────────────┘ - ▲ ▲ ▲ - │ │ │ - └────────────────────────┴────────────────────────┘ - IPC-CFX (Vertical) - MES / ERP / Cloud Systems -``` - -### Message Types - -- **Board handling:** BoardAvailable, BoardForecast, TransportFinished -- **Machine state:** MachineReady, NotificationData, CheckAlive -- **Work order:** GetWorkOrderData, SetWorkOrderData -- **Configuration:** SupervisoryServiceDescription, QueryBoardInfo +Hermes C++ Library (lib_cpp) +The Hermes Standard (IPC-HERMES-9852) is the modern, non-proprietary TCP/IP and XML-based successor to the legacy SMEMA standard. It enables vendor-independent machine-to-machine communication in SMT assembly lines. This repository provides the official, high-performance C++ implementation of the protocol. ---- +1. Core Repository Map +To navigate the library effectively, refer to the following directory structure: -## Project Structure +src/include/: The Public API. This directory contains the headers required to integrate Hermes into your application. -``` -hermes/ -├── src/ -│ ├── Hermes/ # Library source code -│ │ ├── Makefile # Linux/macOS build -│ │ ├── Makefile.mingw # Windows (MinGW) build -│ │ └── *.cpp # Implementation files -│ └── include/ # Public headers -│ └── Hermes.h -├── test/ -│ └── BoostTestHermes/ # Official test suite (optional) -│ ├── Makefile -│ └── Makefile.mingw -├── References/ # Dependencies (not in repo) -│ ├── boost/ # Boost headers -│ └── pugixml/ # Pugixml source -├── docs/ # Documentation -├── setup_windows.bat # Windows build script -├── setup.sh # Linux/macOS build script -└── README.md -``` +Hermes.hpp: The primary Object-Oriented C++ wrapper. ---- +Hermes.h: The low-level C-style API. -## Dependencies Setup +src/Hermes/: The Implementation. Contains the core logic, including the ASIO networking stack, XML serialization, and the standard-compliant state machines. -### Boost (Headers Only) +test/BoostTestHermes/: Verification Suite. Contains comprehensive unit and integration tests. This is the source of truth for protocol-compliant behavior. -**Download:** [Boost 1.78.0](https://archives.boost.io/release/1.78.0/source/boost_1_78_0.zip) +References/: External Headers. Contains local versions of pugixml and boost headers necessary for compilation in restricted environments. -``` -# Extract and copy: -boost_1_78_0/boost/ → References/boost/ -``` +2. Technical Requirements +Dependencies +C++ Standard: C++17 or higher. -### Pugixml +Networking: Boost.ASIO (v1.66 through v1.78 recommended). -**Download:** [Pugixml Latest](https://github.com/zeux/pugixml/releases) +Note: Versions 1.87+ require the -DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT flag. -``` -# Copy source files: -pugixml-*/src/*.hpp → References/pugixml/ -pugixml-*/src/*.cpp → References/pugixml/ -``` +XML Processing: pugixml (included in References/). ---- +Platform Independence +The library is designed to be fully platform-independent. It supports: -## Testing +Windows: MSVC (2017+) and MinGW-w64. -### Simple Test (Recommended) +Linux: GCC (7+) and Clang. -```batch -# Creates basic connectivity test -g++ -std=c++17 -I src/include -L . -o simple_test.exe simple_test.cpp -lHermes -lws2_32 -lmswsock -simple_test.exe -``` +Build System: The project utilizes CMake (3.15+) to generate native build files (Visual Studio Solutions or Makefiles) for your specific platform. -### Official Test Suite (Optional) +3. Unified Build Process (CMake) +To build the library on any platform, use the following standard workflow: -Requires Boost.Test library. See [BUILDING.md](docs/BUILDING.md) for details. +Generate: mkdir build && cd build && cmake .. -```batch -cd test/BoostTestHermes -make -f Makefile.mingw -./BoostTestHermes.exe -``` +Build: cmake --build . --config Release ---- +Outputs: -## Compatibility +hermes.dll / hermes.lib (Windows) -### Hermes Standard Versions +libhermes.so (Linux) -- IPC-HERMES-9852 v1.0 - v1.5 -- SMEMA backward compatibility mode +4. API Architecture & Design +The Callback Model +The C++ API follows a strict Interface-Based Callback pattern. -### Platforms +Users must implement classes inheriting from IDownstreamCallback (Receivers) or IUpstreamCallback (Senders). -| Platform | Compiler | Status | -|----------|----------|--------| -| Windows 10/11 | MinGW-w64 (GCC 7+) | Tested | -| Windows 10/11 | MSVC 2019+ | ⚠️ Community contributions welcome | -| Linux (Ubuntu/Debian) | GCC 7+ | Tested | -| Linux (Fedora/RHEL) | GCC 7+ | Tested | +All pure virtual methods in these interfaces must be overridden to handle protocol events (Connection, Disconnection, Board Available, Machine Ready, etc.). -### Boost Version Compatibility +Execution Model +Blocking Event Loop: The core processing occurs within the Run() method. -⚠️ **IMPORTANT:** Use Boost 1.66 - 1.78 only +Threading: Because Run() blocks the calling thread to process network I/O, it must be executed in a dedicated background thread to maintain application responsiveness. -- [NO] Boost 1.87+ breaks compatibility (removed `io_service`) -- [OK] Boost 1.78 recommended (stable, well-tested) +State Management: The library handles all internal state transitions (e.g., Not Connected -> Service Description -> Not Ready -> Ready) automatically based on the Enable() configuration. ---- +5. Supported Communication Channels +The library implements the four primary channels defined by IPC-HERMES-9852: -## IPC Standards Integration +Upstream: Machine-to-Machine (Sending). -### Hermes + CFX Together +Downstream: Machine-to-Machine (Receiving). -The Hermes Standard drives horizontal integration along the SMT line. IPC-CFX complements this by providing a powerful standard for connecting vertically from the SMT line to an MES. +Configuration: Exchange of machine capabilities and line settings. -**Horizontal (Line):** IPC-HERMES-9852 (this library) -**Vertical (MES/ERP):** IPC-2591 (CFX) - separate implementation +Vertical: Communication with factory-level MES/ERP systems. ---- +6. Deployment Topologies +The library supports standard IPv4 networking across various hardware setups: -## Support & Contributing +Point-to-Point: Direct Ethernet connection between two machines. -- **Issues:** [GitHub Issues](../../issues) -- **Documentation:** [Wiki](../../wiki) -- **Official Standard:** [IPC-HERMES-9852](https://www.the-hermes-standard.info/) +Switched Fabric: Deployment via factory-wide network switches. This is the preferred method for modern "Smart Factory" environments, as it allows a single network interface to handle both horizontal (machine) and vertical (MES) data simultaneously. -### Contributing +7. Quality Assurance +All protocol features are verified against the BoostTestHermes suite. -Contributions welcome! Please: -1. Follow existing code style -2. Add tests for new features -3. Update documentation -4. Submit pull requests +Unit Tests: Verify individual XML serialization and data structures. + +Integration Tests: Simulate full handshakes between virtual Upstream and Downstream sessions. + +Automation: Compatible with ctest for continuous integration workflows. + +License: Copyright (c) ASM Assembly Systems GmbH & Co. KG. Licensed under the Apache License, Version 2.0. See COPYRIGHT.txt for details. \ No newline at end of file diff --git a/References/pugixml/README.txt b/References/pugixml/README.txt deleted file mode 100644 index dcf6bb4..0000000 --- a/References/pugixml/README.txt +++ /dev/null @@ -1,6 +0,0 @@ -PugiXML library - -For Windows, copy the Pugi headers, sources and link-libraries to this location - -For Linux, this directory is not needed, use the PugiXML packages of your system -instead (e.g. libpugixml-dev) diff --git a/docs/01_Architecture_Overview.md b/docs/01_Architecture_Overview.md new file mode 100644 index 0000000..a0533f5 --- /dev/null +++ b/docs/01_Architecture_Overview.md @@ -0,0 +1,45 @@ +# 01. Architecture Overview + +The Hermes Standard (IPC-HERMES-9852) is a protocol based on TCP/IP and XML designed to replace the legacy SMEMA wiring standard. This C++ library serves as a robust wrapper around the protocol, handling the low-level socket connections, XML serialization, and the strict state machines required for compliance. + +To effectively use this library, you must understand three core architectural concepts: **Machine Roles**, the **Event-Driven Model**, and **Thread Management**. + +--- + +## 1. Machine Roles (Network Topology) + +In a Hermes line, machines communicate horizontally (machine-to-machine) and vertically (machine-to-factory). The library provides dedicated classes for each role: + +### Horizontal Integration (The SMT Line) +* **`Downstream` (The Receiver):** Acts as a **TCP Server**. A downstream machine (e.g., a Pick & Place) opens a specific port and listens. It waits for the previous machine in the line to connect and hand over a board. +* **`Upstream` (The Sender):** Acts as a **TCP Client**. An upstream machine (e.g., a Printer) actively connects to the IP address and port of the next machine in the line to send a board. + +*Note: Most machines in the middle of a line will instantiate **both** an `Upstream` object (to send to the right) and a `Downstream` object (to receive from the left).* + +### Vertical Integration (The Factory) +* **`ConfigurationService`:** Used to exchange machine capabilities and supervisory line control data. +* **`VerticalService`:** Acts as a TCP Server to provide real-time board tracking data to higher-level MES/ERP systems (often working alongside IPC-CFX). + +--- + +## 2. The Event-Driven Model + +The library is entirely asynchronous and event-driven. You do not write code that says `WaitForBoard()`. Instead, you register **Callbacks** that the library triggers when network events occur. + +If you are using the modern wrapper (`HermesModern.hpp`), these events are handled via standard C++ lambdas: + +```cpp +// Example: The library triggers this lambda when an XML message arrives +downstream.RegisterBoardAvailableCallback([](const Hermes::BoardAvailableData& board) { + std::cout << "Board Arrived! Barcode: " << board._topBarcode << std::endl; +}); +3. Thread Management (Critical) +Because the library utilizes Boost.ASIO for high-performance networking, it relies on a continuous event loop to process incoming socket data. + +The Run() Loop: All network processing happens inside the Run() method of the Upstream or Downstream objects. + +Blocking Behavior: The Run() method is blocking. If you call it on your main application thread, your application's UI or main control loop will freeze. + +The Solution: You must always execute the core Hermes loop in a dedicated background std::thread. + +(Note: If you use the HermesModern.hpp wrapper, this background thread is automatically managed for you when you call Enable()). \ No newline at end of file diff --git a/docs/02_Building_and_Linking.md b/docs/02_Building_and_Linking.md new file mode 100644 index 0000000..d55b451 --- /dev/null +++ b/docs/02_Building_and_Linking.md @@ -0,0 +1,52 @@ +# 02. Building and Linking + +The Hermes library uses **CMake (3.15+)** as its universal build system, ensuring seamless, cross-platform compilation on Windows, Linux, and macOS. + +--- + +## 1. System Requirements + +Before you begin, verify your environment has the following: +* **C++17 Compiler:** MSVC 2017+, GCC 7+, or Clang 5+. +* **Boost Libraries:** Version 1.66 through 1.78 is recommended. The library specifically requires `boost_system` and `boost_thread`. + * *Note on Boost 1.87+: If using a newer version of Boost where `io_service` is deprecated, our CMake setup automatically applies the `-DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT` flag to ensure it still compiles.* +* **pugixml:** No installation required. The required files are bundled locally in the `References/` folder. + +--- + +## 2. Compiling the Library + +Open a terminal in the root of the cloned repository and execute a standard out-of-source build: + +```bash +mkdir build && cd build + +# 1. Configure the project (CMake locates Boost and your compiler) +cmake .. + +# 2. Compile the core library +cmake --build . --config Release +Build Outputs: + +Windows: build/Release/hermes.dll and hermes.lib + +Linux: build/libhermes.so + +3. Linking Hermes to Your Project +The cleanest way to integrate Hermes into your own C++ application is by using CMake's add_subdirectory command. + +Assuming you cloned this repository into a folder named third_party/lib_cpp inside your project, your application's CMakeLists.txt would look like this: + +CMake +cmake_minimum_required(VERSION 3.15) +project(MySmtMachine) + +# 1. Include the Hermes library directory +add_subdirectory(third_party/lib_cpp) + +# 2. Define your application executable +add_executable(MyApp main.cpp) + +# 3. Link Hermes to your application +# This automatically configures the include paths for Hermes.hpp +target_link_libraries(MyApp PRIVATE hermes) \ No newline at end of file diff --git a/docs/03_Modern_API_Basics.md b/docs/03_Modern_API_Basics.md new file mode 100644 index 0000000..34b5836 --- /dev/null +++ b/docs/03_Modern_API_Basics.md @@ -0,0 +1,55 @@ +# 03. Modern API Basics + +The legacy Hermes C++ library relies on strict class inheritance (`IDownstreamCallback` and `IUpstreamCallback`) and manual thread management. To simplify development, we provide `HermesModern.hpp`, a wrapper that allows you to use modern C++17 lambdas and automatically manages the background networking threads. + +--- + +## 1. Instantiating the Node + +Every Hermes connection requires a `laneId` (typically `1` for a standard single-lane machine). You instantiate either a `Downstream` (Receiver) or `Upstream` (Sender) object from the `Hermes::Modern` namespace. + +```cpp +#include "HermesModern.hpp" + +// Create a receiver (listens for the previous machine) on Lane 1 +Hermes::Modern::Downstream receiver(1); + +// Create a sender (connects to the next machine) on Lane 1 +Hermes::Modern::Upstream sender(1); +2. Registering Callbacks +Instead of overriding pure virtual functions in a separate class, you register lambda functions directly to the object. You only need to register the callbacks your machine actually cares about. + +C++ +// Handle successful TCP connections +receiver.RegisterConnectedCallback([](const Hermes::ConnectionInfo& info) { + std::cout << "Connected to: " << info.m_hostAddress << "\n"; +}); + +// Handle incoming board data +receiver.RegisterBoardAvailableCallback([](const Hermes::BoardAvailableData& board) { + std::cout << "Incoming Board ID: " << board.m_boardId << "\n"; +}); + +// Handle disconnection or errors +receiver.RegisterDisconnectedCallback([](const Hermes::Error& err) { + std::cout << "Disconnected. Reason: " << err.m_text << "\n"; +}); +3. Configuration and Execution (Enable) +To start listening (Downstream) or actively connecting (Upstream), you must populate a settings object and pass it to the Enable() method. + +Crucially, calling Enable() in the modern wrapper automatically spawns the required background std::thread to process network events. You do not need to manage the blocking Run() loop yourself. + +C++ +Hermes::DownstreamSettings settings; +settings.m_machineId = "My_Pick_And_Place"; +settings.m_clientAddress = "192.168.1.100"; // Accept connections from this IP +settings.m_port = 50101; // Standard Hermes Default Port + +// Start the network event loop in the background +receiver.Enable(settings); +4. Shutting Down (Stop) +When your application is closing, or if you need to sever the connection to change line configurations, call Stop(). This will cleanly disconnect the socket, send the appropriate XML termination messages, and safely join the background thread. + +C++ +// Safely shut down the connection and stop the background thread +receiver.Stop(); \ No newline at end of file diff --git a/docs/04_Downstream_Receiver.md b/docs/04_Downstream_Receiver.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/05_Upstream_Sender.md b/docs/05_Upstream_Sender.md new file mode 100644 index 0000000..fda4ee4 --- /dev/null +++ b/docs/05_Upstream_Sender.md @@ -0,0 +1,72 @@ +# 05. Upstream Sender (The Handshake) + +An **Upstream** node acts as the sending machine (e.g., a printer sending a board to a pick-and-place). It operates as a TCP Client, actively attempting to connect to the IP address and port of the next machine in the line. + +To successfully hand over a board, you must follow the exact reverse of the Downstream handshake. + +--- + +## 1. The Standard Handshake Sequence + +If you send messages out of sequence, the receiving machine is required by the Hermes Standard to drop the connection. + +| Upstream (Sender - YOU) | Direction | Downstream (Receiver) | +| :--- | :---: | :--- | +| `ServiceDescription` | ➔ | *(Establish protocol version)* | +| | ⬅ | `MachineReady` *(I have space for a board)* | +| `BoardAvailable` | ➔ | *(Contains Barcode/Dimensions)* | +| | ⬅ | `StartTransport` *(Conveyors turning, send it!)* | +| `TransportFinished`| ➔ | *(Board has left my machine)* | +| | ⬅ | `StopTransport` *(Board is fully inside me)* | + +--- + +## 2. Implementing the Sender Logic + +Using the `HermesModern.hpp` wrapper, here is how you implement a compliant sender. + +*(Note: In a real machine application, you will also integrate your physical sensors—like board edge detectors—into this logic flow).* + +```cpp +#include "HermesModern.hpp" +#include + +Hermes::Modern::Upstream sender(1); +const unsigned CURRENT_SESSION = 1; + +// 1. Wait for the Downstream machine to say it is empty +sender.RegisterMachineReadyCallback([&](const Hermes::MachineReadyData& data) { + std::cout << "Downstream is ready to receive a board.\n"; + + // 2. When your machine finishes processing a board, announce it + Hermes::BoardAvailableData boardData; + boardData.m_boardId = "PCB_12345"; + boardData.m_topBarcode = "SN-998877"; + boardData.m_lengthInMM = 150.0f; + boardData.m_widthInMM = 100.0f; + sender.Signal(CURRENT_SESSION, boardData); +}); + +// 3. Downstream has started its conveyors and says "Send it" +sender.RegisterStartTransportCallback([&](const Hermes::StartTransportData& data) { + std::cout << "Downstream conveyors running at " << data.m_conveyorSpeed << " mm/s\n"; + + // 4. Start YOUR conveyors to physically move the board out. + // ... (Wait for hardware sensor to confirm board left the machine) ... + + // 5. Tell the Downstream machine the board is fully on their side + Hermes::TransportFinishedData finishedData; + finishedData.m_transferState = Hermes::ETransferState::eCOMPLETE; + sender.Signal(CURRENT_SESSION, finishedData); +}); + +// 6. Downstream confirms the board has safely arrived inside +sender.RegisterStopTransportCallback([&](const Hermes::StopTransportData& data) { + std::cout << "Handover Complete! Board safely delivered.\n"; + + // You are now free to process the next board. +}); +3. Handling Exceptions +RevokeMachineReady: If you receive this before sending BoardAvailable, it means the downstream machine suddenly became unavailable (e.g., an operator hit the Emergency Stop). You must wait until you receive a new MachineReady message before trying to send your board. + +Connection Loss: If the connection drops during transport, halt your conveyors immediately. \ No newline at end of file diff --git a/docs/06_Core_Message_Types.md b/docs/06_Core_Message_Types.md new file mode 100644 index 0000000..376b066 --- /dev/null +++ b/docs/06_Core_Message_Types.md @@ -0,0 +1,72 @@ +# 06. Core Message Types & Data Dictionary + +The Hermes Standard defines specific payloads for every stage of the handshake. This document provides a quick reference for the exact C++ structs and fields you will interact with in `HermesData.hpp`. + +## ⚠️ Important Note on `Hermes::Optional` +To maintain compatibility with older C++ compilers, this library does not use `std::optional`. It uses a custom `Hermes::Optional`. To safely read optional fields like barcodes or dimensions, check them like a boolean and dereference them with `*`: + +```cpp +if (boardData.m_optionalTopBarcode) { + std::string barcode = *boardData.m_optionalTopBarcode; +} +1. Initialization Messages +ServiceDescriptionData +Exchanged immediately upon TCP connection to verify compatibility. + +std::string m_machineId: The name/ID of the machine. + +unsigned m_laneId: The specific lane (usually 0 or 1). + +std::string m_version: Protocol version (Defaults to "1.5"). + +SupportedFeatures m_supportedFeatures: Flags for advanced features. + +2. Board Handover Messages +BoardAvailableData (Sent by Upstream) +Announces a board is waiting to be sent. Contains physical properties. +Required Fields: + +std::string m_boardId: Unique UUID for the board. + +std::string m_boardIdCreatedBy: Machine ID that generated the UUID. + +EBoardQuality m_failedBoard: eANY, eGOOD, or eBAD. + +EFlippedBoard m_flippedBoard: eSIDE_UP_IS_UNKNOWN, eTOP_SIDE_IS_UP, eBOTTOM_SIDE_IS_UP. + +Common Optional Fields: + +Optional m_optionalTopBarcode / m_optionalBottomBarcode + +Optional m_optionalLengthInMM / m_optionalWidthInMM / m_optionalThicknessInMM + +Optional m_optionalWorkOrderId / m_optionalBatchId + +MachineReadyData (Sent by Downstream) +Announces the receiver is empty and ready. + +EBoardQuality m_failedBoard: Specifies what quality the machine accepts (e.g., eGOOD). + +Note: This struct also contains the same optional physical dimensions as BoardAvailableData. Downstream machines can populate these to demand specific board sizes. + +3. Transport Control Messages +StartTransportData (Sent by Downstream) +Commands the Upstream machine to begin pushing the board. + +std::string m_boardId: The UUID of the board being requested. + +Optional m_optionalConveyorSpeedInMMPerSecs: The speed the sender should match. + +TransportFinishedData (Sent by Upstream) +Confirms the board has entirely left the sender's conveyor. + +ETransferState m_transferState: Usually ETransferState::eCOMPLETE. + +std::string m_boardId: The UUID of the transferred board. + +StopTransportData (Sent by Downstream) +Confirms the board has securely arrived inside the receiver. + +ETransferState m_transferState: Usually ETransferState::eCOMPLETE. + +std::string m_boardId: The UUID of the received board. \ No newline at end of file diff --git a/docs/07_Network_and_Topology.md b/docs/07_Network_and_Topology.md new file mode 100644 index 0000000..dcc16e5 --- /dev/null +++ b/docs/07_Network_and_Topology.md @@ -0,0 +1,67 @@ +# 07. Network Configuration & Topology + +Hermes operates over standard TCP/IP Ethernet. Because it uses standard networking rather than proprietary cables, machines can be wired together in different ways depending on the factory's infrastructure. + +This document explains the physical setups and how to configure your `UpstreamSettings` and `DownstreamSettings` structs to match them. + +--- + +## 1. Physical Topologies + +### Point-to-Point (Direct Connection) +This is the simplest setup and the direct equivalent of legacy SMEMA. Two adjacent machines are connected directly to each other using a standard Ethernet cable. +* **Pros:** Extremely secure; no interference from other factory traffic. +* **Cons:** Requires the machine to have multiple network interface cards (NICs) if it also needs to talk to the factory network (MES). + +### Switched Fabric (Smart Factory) +All machines in the line plug into a central factory Ethernet switch. +* **Pros:** A single physical cable handles both horizontal (machine-to-machine) and vertical (machine-to-MES) data. +* **Cons:** Requires strict IP filtering to ensure Machine A doesn't accidentally send a board to Machine C. + +--- + +## 2. The Hermes Port Standard + +By definition (IPC-HERMES-9852), the TCP port used for a connection is derived from the Lane ID. +* The Base Port is always **50100**. +* Port = Base Port + Lane ID. +* **Therefore, a standard single-lane machine ALWAYS communicates on Port 50101.** + +--- + +## 3. Configuring the Receiver (`DownstreamSettings`) + +The Receiver acts as a TCP Server. It opens a port and waits. + +```cpp +Hermes::DownstreamSettings settings; +settings.m_machineId = "Oven_01"; +settings.m_port = 50101; + +// OPTIONAL BUT RECOMMENDED IN SWITCHED NETWORKS: +// If you leave this blank, ANY machine on the factory floor can connect to your receiver. +// By setting this, you force the socket to ONLY accept connections from the specific +// IP address of the machine physically positioned before you in the line. +settings.m_optionalClientAddress = "192.168.1.50"; + +// Advanced keep-alive timings (defaults are usually fine) +settings.m_checkAlivePeriodInSeconds = 60.0; +settings.m_reconnectWaitTimeInSeconds = 10.0; +4. Configuring the Sender (UpstreamSettings) +The Sender acts as a TCP Client. It actively reaches out across the network to find the next machine. + +C++ +Hermes::UpstreamSettings settings; +settings.m_machineId = "Printer_01"; +settings.m_port = 50101; + +// REQUIRED: +// You must explicitly tell the sender the IP address of the Downstream +// receiver it is supposed to push boards into. +settings.m_hostAddress = "192.168.1.51"; + +// Advanced keep-alive timings +settings.m_checkAlivePeriodInSeconds = 60.0; +settings.m_reconnectWaitTimeInSeconds = 10.0; +5. Network Resilience +The library handles network drops automatically. If a cable is unplugged, the OnDisconnected callback will fire. The Upstream node will then automatically attempt to reconnect to the m_hostAddress every m_reconnectWaitTimeInSeconds (default: 10 seconds) until the connection is restored. \ No newline at end of file diff --git a/docs/08_Legacy_Interface_API.md b/docs/08_Legacy_Interface_API.md new file mode 100644 index 0000000..00306d7 --- /dev/null +++ b/docs/08_Legacy_Interface_API.md @@ -0,0 +1,93 @@ +# 08. Legacy Interface API (Advanced) + +While the `HermesModern.hpp` wrapper simplifies development using lambdas, advanced users may prefer to interact directly with the raw C++ library to minimize overhead or to integrate Hermes deeply into an existing class hierarchy. + +To use the raw API, you must inherit from the core interfaces and manage the blocking event loops manually. + +--- + +## 1. The Core Interfaces + +The library provides two primary abstract classes: +* `Hermes::IDownstreamCallback` +* `Hermes::IUpstreamCallback` + +If you inherit directly from these, your compiler will force you to implement every pure virtual method (e.g., `OnConnected`, `OnState`, and the various overloaded `On` methods for data). + +--- + +## 2. The Naming Collision Problem + +In a real factory, most machines sit in the middle of the line. This means your application class will likely need to act as both a Sender and a Receiver. + +If you try to inherit from both base interfaces simultaneously, you will encounter **ambiguous function names**. For example, both interfaces define this exact method: +`virtual void On(unsigned sessionId, EState state, const ServiceDescriptionData& data) = 0;` + +The compiler won't know if you are receiving a service description from the previous machine or the next machine. + +--- + +## 3. The "Helper" Solution + +To solve the naming collision, the library provides two specialized helper structs in `Hermes.hpp`: +* `Hermes::UpstreamCallbackHelper` +* `Hermes::DownstreamCallbackHelper` + +These helpers internally catch the ambiguous `On()` methods and reroute them to uniquely named functions like `OnUpstream(...)` and `OnDownstream(...)`. + +--- + +## 4. Legacy Implementation Example + +Here is how you use the helpers to build a machine that sends and receives simultaneously using the raw API: + +```cpp +#include "Hermes.hpp" +#include +#include + +// 1. Inherit from both Helpers, NOT the base interfaces +class MySmtMachine : public Hermes::UpstreamCallbackHelper, + public Hermes::DownstreamCallbackHelper +{ +public: + // 2. Instantiate the core objects, passing `*this` as the callback reference + MySmtMachine() : m_upstream(1, *this), m_downstream(1, *this) {} + + void Start() { + // 3. YOU must manage the blocking threads manually + m_upThread = std::thread([this]() { m_upstream.Run(); }); + m_downThread = std::thread([this]() { m_downstream.Run(); }); + + Hermes::UpstreamSettings upSettings("MyMachine", "192.168.1.51", 50101); + Hermes::DownstreamSettings downSettings("MyMachine", 50101); + + m_upstream.Enable(upSettings); + m_downstream.Enable(downSettings); + } + + void Stop() { + m_upstream.Stop(); + m_downstream.Stop(); + if (m_upThread.joinable()) m_upThread.join(); + if (m_downThread.joinable()) m_downThread.join(); + } + +protected: + // 4. Implement the uniquely named virtual methods + void OnUpstreamConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { + std::cout << "Sender Connected to next machine!\n"; + } + + void OnDownstreamConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { + std::cout << "Receiver Accepted connection from previous machine!\n"; + } + + // ... You must implement all remaining OnUpstream and OnDownstream virtuals ... + +private: + Hermes::Upstream m_upstream; + Hermes::Downstream m_downstream; + std::thread m_upThread; + std::thread m_downThread; +}; \ No newline at end of file diff --git a/docs/09_Vertical_MES_Integration.md b/docs/09_Vertical_MES_Integration.md new file mode 100644 index 0000000..4788afb --- /dev/null +++ b/docs/09_Vertical_MES_Integration.md @@ -0,0 +1,86 @@ +# 09. Vertical Integration (MES/ERP) + +While `Upstream` and `Downstream` handle **Horizontal** communication (machine passing boards to machine), the Hermes Standard also defines a **Vertical** channel. This allows higher-level factory software (like an MES, ERP, or line controller) to monitor and control the machines in real-time. + +In the ASM `lib_cpp` library, this is handled by the `VerticalService` and `VerticalClient` classes. + +--- + +## 1. The Architecture + +In a standard smart factory setup: +* **The Machine (You):** Acts as the **`VerticalService`** (TCP Server). You open a port (often `1248` or `50100`) and wait. +* **The MES/Factory Cloud:** Acts as the **`VerticalClient`** (TCP Client). It connects to every machine on the line to gather data. + +--- + +## 2. The Vertical Handshake + +When an MES connects to your machine, you must exchange supervisory descriptions. This is entirely separate from the machine-to-machine horizontal handshake. + +1. **MES Connects:** Your `OnConnected` callback fires. +2. **Exchange Descriptions:** The MES sends its `SupervisoryServiceDescriptionData`. Your machine responds with its own, detailing what features it supports (e.g., `FeatureBoardTracking`, `FeatureQueryWorkOrderInfo`). + +--- + +## 3. Real-Time Board Tracking + +The most common use case for Vertical Integration is letting the factory know exactly where every PCB is located. If the MES enables `FeatureBoardTracking`, your machine is responsible for firing off two specific messages during its operation: + +### A. `BoardArrivedData` +You must signal this to the `VerticalService` the moment a board successfully enters your machine. +**Key Fields:** +* `m_machineId`: Your machine's ID. +* `m_boardId`: The UUID of the board that just entered. +* `m_boardTransfer`: How it got there (e.g., `EBoardArrivedTransfer::eTRANSFERRED` from an upstream machine, or `eLOADED` if an operator manually placed it). + +### B. `BoardDepartedData` +You must signal this the moment a board completely leaves your machine. +**Key Fields:** +* `m_machineId`: Your machine's ID. +* `m_boardId`: The UUID of the board. +* `m_boardTransfer`: How it left (e.g., `EBoardDepartedTransfer::eTRANSFERRED` to a downstream machine, or `eREMOVED` if an operator took it out). + +--- + +## 4. Advanced: Work Orders and Routing + +If your machine supports dynamic recipes or routing, the Vertical channel allows you to ask the MES what to do with a board. + +1. **Query:** When a board arrives, your machine sends a `QueryWorkOrderInfoData` containing the board's barcode. +2. **Reply:** The MES responds with `ReplyWorkOrderInfoData`, telling your machine which recipe/program to load for that specific barcode. +3. **Route:** The MES can also inject `m_route` attributes, telling a sorting conveyor whether to pass the board through, send it to an inspection queue, or reject it to a scrap bin. + +--- + +## 5. Raw Implementation Example + +If you are using the raw C++ interface (bypassing lambdas), your vertical implementation will look like this: + +```cpp +#include "Hermes.hpp" + +class MyLineMonitor : public Hermes::IVerticalServiceCallback { +public: + MyLineMonitor() : m_verticalService(*this) {} + + void Start() { + Hermes::VerticalServiceSettings settings("My_Machine", 1248); + m_verticalService.Enable(settings); + // Ensure you run m_verticalService.Run() in a background thread! + } + + // --- Override the pure virtuals --- + void OnConnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::ConnectionInfo& info) override { + // MES has connected to us + } + + void On(unsigned sessionId, Hermes::EVerticalState state, const Hermes::SupervisoryServiceDescriptionData& data) override { + // MES told us who it is. We can now reply with our capabilities. + } + + // ... Implement remaining virtuals ... + +private: + Hermes::VerticalService m_verticalService; +}; \ No newline at end of file diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md deleted file mode 100644 index 6f9cbc0..0000000 --- a/docs/API_REFERENCE.md +++ /dev/null @@ -1,682 +0,0 @@ -# Hermes API Reference - -Complete API documentation for the IPC-HERMES-9852 library. - ---- - -## Table of Contents - -- [Core Classes](#core-classes) - - [Upstream](#upstream) - - [Downstream](#downstream) - - [ConfigurationService](#configurationservice) -- [Data Structures](#data-structures) -- [Enumerations](#enumerations) -- [Callbacks](#callbacks) - ---- - -## Core Classes - -### Upstream - -The `Upstream` class manages connections to downstream machines (sending boards forward in the line). - -#### Methods - -##### `void Connect(unsigned laneId, const UpstreamSettings& settings)` - -Establishes connection to a downstream machine. - -**Parameters:** -- `laneId` - Unique identifier for this lane/track (1-based) -- `settings` - Connection configuration - -**Example:** -```cpp -Hermes::Upstream upstream; - -Hermes::UpstreamSettings settings; -settings._laneId = 1; -settings._hostAddress = "192.168.1.20"; -settings._port = 50101; -settings._checkAliveResponseTimeout = 30.0; -settings._reconnectWaitTime = 10.0; - -upstream.Connect(1, settings); -``` - -##### `void Disconnect(unsigned laneId)` - -Closes connection to downstream machine. - -**Parameters:** -- `laneId` - Lane identifier to disconnect - -**Example:** -```cpp -upstream.Disconnect(1); -``` - -##### `void Signal(unsigned laneId, const T& data)` - -Sends a message to the downstream machine. Type `T` can be: -- `BoardAvailableData` - Notify board is ready to transfer -- `BoardForecastData` - Advance notice of upcoming board -- `NotificationData` - Status, warning, or error message -- `MachineReadyData` - Ready to receive next board -- `TransportFinishedData` - Board transfer complete -- `CheckAliveData` - Heartbeat/keepalive - -**Example:** -```cpp -// Signal board available -Hermes::BoardAvailableData board; -board._boardId = "PCB-12345"; -board._topBarcode = "BARCODE-001"; -board._lengthInMM = 250.0; -board._widthInMM = 200.0; -board._thickness = 1.6; -board._conveyorSpeed = 200.0; -board._topClearanceHeight = 15.0; -board._bottomClearanceHeight = 15.0; -board._weight = 0.5; -board._workOrderId = "WO-001"; -board._failedBoard = Hermes::EBoardQuality::eGOOD; - -upstream.Signal(1, board); - -// Signal notification -Hermes::NotificationData notification; -notification._notificationCode = Hermes::ENotificationCode::eMACHINE_READY; -notification._severity = Hermes::ESeverity::eINFO; -notification._description = "Machine initialized successfully"; - -upstream.Signal(1, notification); -``` - -#### Settings Structure - -```cpp -struct UpstreamSettings { - unsigned _laneId; // Lane identifier - std::string _machineId; // This machine's ID - std::string _hostAddress; // Downstream IP address - uint16_t _port; // Downstream port (default: 50101) - double _checkAliveResponseTimeout; // Timeout in seconds (default: 30.0) - double _reconnectWaitTime; // Reconnect delay (default: 10.0) -}; -``` - ---- - -### Downstream - -The `Downstream` class listens for connections from upstream machines (receiving boards). - -#### Methods - -##### `void Enable(unsigned laneId, const DownstreamSettings& settings)` - -Starts listening for upstream machine connections. - -**Parameters:** -- `laneId` - Unique identifier for this lane/track -- `settings` - Listener configuration - -**Example:** -```cpp -Hermes::Downstream downstream; - -Hermes::DownstreamSettings settings; -settings._laneId = 1; -settings._machineId = "MACHINE-02"; -settings._port = 50101; -settings._checkAliveResponseTimeout = 30.0; - -downstream.Enable(1, settings); -``` - -##### `void Disable(unsigned laneId)` - -Stops listening and disconnects any connected upstream machines. - -**Parameters:** -- `laneId` - Lane identifier to disable - -**Example:** -```cpp -downstream.Disable(1); -``` - -##### `void Signal(unsigned laneId, const T& data)` - -Sends a message to the connected upstream machine. Type `T` can be: -- `MachineReadyData` - Ready to accept next board -- `RevokeMachineReadyData` - Cancel ready state -- `StartTransportData` - Begin board transfer -- `StopTransportData` - Stop board movement -- `NotificationData` - Status, warning, or error -- `CheckAliveData` - Heartbeat response - -**Example:** -```cpp -// Signal ready to receive -Hermes::MachineReadyData ready; -ready._boardForecast = Hermes::EBoardForecast::eAVAILABLE; -ready._failedBoard = Hermes::EBoardQuality::eGOOD; - -downstream.Signal(1, ready); -``` - -##### `void RegisterCallback(unsigned laneId, std::function callback)` - -Registers a callback to be invoked when specific message types are received. - -**Callback Types:** -- `BoardAvailableData` - Upstream has board ready -- `BoardForecastData` - Upcoming board notification -- `TransportFinishedData` - Board transfer completed -- `NotificationData` - Upstream status/error -- `CheckAliveData` - Heartbeat received - -**Example:** -```cpp -// Register board arrival handler -downstream.RegisterCallback(1, - [](const Hermes::BoardAvailableData& board) { - std::cout << "Board arrived: " << board._boardId << std::endl; - std::cout << " Size: " << board._lengthInMM << "x" - << board._widthInMM << "mm" << std::endl; - std::cout << " Barcode: " << board._topBarcode << std::endl; - } -); - -// Register notification handler -downstream.RegisterCallback(1, - [](const Hermes::NotificationData& notification) { - std::cout << "Notification: " << notification._description << std::endl; - } -); -``` - -##### `void RegisterConnectionCallbacks(unsigned laneId, std::function onConnect, std::function onDisconnect)` - -Registers callbacks for connection state changes. - -**Example:** -```cpp -downstream.RegisterConnectionCallbacks(1, - []() { - std::cout << "Upstream machine connected" << std::endl; - }, - []() { - std::cout << "Upstream machine disconnected" << std::endl; - } -); -``` - -#### Settings Structure - -```cpp -struct DownstreamSettings { - unsigned _laneId; // Lane identifier - std::string _machineId; // This machine's ID - uint16_t _port; // Listen port (default: 50101) - std::string _optionalClientAddress; // Filter connections from this IP - double _checkAliveResponseTimeout; // Timeout in seconds (default: 30.0) -}; -``` - ---- - -### ConfigurationService - -Manages configuration data exchange and supervisory communication. - -#### Methods - -##### `void Enable(unsigned laneId, const ConfigurationServiceSettings& settings)` - -Starts configuration service listener. - -**Parameters:** -- `laneId` - Lane identifier -- `settings` - Service configuration - -**Example:** -```cpp -Hermes::ConfigurationService config; - -Hermes::ConfigurationServiceSettings settings; -settings._port = 1248; // Default configuration port -settings._machineId = "MACHINE-01"; - -config.Enable(1, settings); -``` - -##### `void Disable(unsigned laneId)` - -Stops configuration service. - -##### `void Signal(unsigned laneId, const T& data)` - -Sends configuration messages. Type `T` can be: -- `ServiceDescriptionData` - Machine capabilities -- `BoardAvailableData` - Board info response -- `SetConfigurationData` - Apply configuration -- `GetConfigurationData` - Request configuration -- `CurrentConfigurationData` - Configuration response - -**Example:** -```cpp -// Send service description -Hermes::ServiceDescriptionData description; -description._machineId = "MACHINE-01"; -description._laneId = 1; -description._supportedFeatures._boardForecast = true; -description._supportedFeatures._queryWorkOrderInfo = true; - -config.Signal(1, description); - -// Request work order data -Hermes::GetWorkOrderDataRequest request; -request._workOrderId = "WO-12345"; - -config.Signal(1, request); -``` - -#### Settings Structure - -```cpp -struct ConfigurationServiceSettings { - std::string _machineId; // Machine identifier - uint16_t _port; // Listen port (default: 1248) - double _checkAliveResponseTimeout; // Timeout in seconds -}; -``` - ---- - -## Data Structures - -### BoardAvailableData - -Describes a PCB board ready for transfer. - -```cpp -struct BoardAvailableData { - std::string _boardId; // Unique board identifier - std::string _boardIdCreatedFrom; // Template/parent board - Hermes::EBoardQuality _failedBoard; // Board quality status - std::string _productTypeId; // Product type - Hermes::EFlipped _flipped; // Board orientation - std::string _topBarcode; // Top side barcode - std::string _bottomBarcode; // Bottom side barcode - double _lengthInMM; // Board length (mm) - double _widthInMM; // Board width (mm) - double _thickness; // Board thickness (mm) - double _conveyorSpeed; // Transport speed (mm/s) - double _topClearanceHeight; // Top clearance (mm) - double _bottomClearanceHeight; // Bottom clearance (mm) - double _weight; // Board weight (kg) - std::string _workOrderId; // Associated work order - std::string _batchId; // Batch identifier -}; -``` - -### BoardForecastData - -Advance notification of an upcoming board. - -```cpp -struct BoardForecastData { - std::string _boardId; // Board identifier - std::string _boardIdCreatedFrom; // Template identifier - double _lengthInMM; // Board length - double _widthInMM; // Board width - double _thickness; // Board thickness - double _conveyorSpeed; // Transport speed - double _topClearanceHeight; // Top clearance - double _bottomClearanceHeight; // Bottom clearance - double _weight; // Board weight - std::string _workOrderId; // Work order - std::string _batchId; // Batch ID - double _timeUntilAvailable; // ETA in seconds -}; -``` - -### MachineReadyData - -Signals downstream machine is ready to accept a board. - -```cpp -struct MachineReadyData { - Hermes::EBoardQuality _failedBoard; // Reject if board quality matches - Hermes::EBoardForecast _boardForecast; // Board forecast capability - std::string _forecastId; // Expected board ID -}; -``` - -### NotificationData - -Status, warning, or error message. - -```cpp -struct NotificationData { - Hermes::ENotificationCode _notificationCode; // Type of notification - Hermes::ESeverity _severity; // Importance level - std::string _description; // Human-readable message -}; -``` - -### TransportFinishedData - -Confirms board transfer completion. - -```cpp -struct TransportFinishedData { - Hermes::ETransferState _transferState; // Success/failure status - std::string _boardId; // Transferred board ID -}; -``` - ---- - -## Enumerations - -### EBoardQuality - -Board quality/status classification. - -```cpp -enum class EBoardQuality { - eUNKNOWN = 0, // Quality not determined - eGOOD = 1, // Board passed all checks - eFAILED = 2 // Board failed quality check -}; -``` - -### EFlipped - -Board orientation. - -```cpp -enum class EFlipped { - eNOT_FLIPPED = 0, // Normal orientation - eFLIPPED = 1 // Board is upside down -}; -``` - -### ETransferState - -Board transfer result. - -```cpp -enum class ETransferState { - eNOT_STARTED = 0, // Transfer not initiated - eINCOMPLETE = 1, // Transfer failed/aborted - eCOMPLETE = 2 // Successfully transferred -}; -``` - -### ENotificationCode - -Predefined notification types. - -```cpp -enum class ENotificationCode { - eUNSPECIFIED = 0, - eMACHINE_READY = 1, - eBOARD_AVAILABLE = 2, - eBOARD_QUALITY_CHANGED = 3, - eCONVEYOR_SPEED_CHANGED = 4, - eMACHINE_PAUSED = 5, - eMACHINE_STOPPED = 6, - eCONNECTION_ERROR = 7, - eBOARD_LOST = 8, - eBOARD_DEFECT = 9 - // ... see Hermes.h for complete list -}; -``` - -### ESeverity - -Notification importance level. - -```cpp -enum class ESeverity { - eFATAL = 1, // Critical error, stop line - eERROR = 2, // Error requiring attention - eWARNING = 3, // Warning, can continue - eINFO = 4 // Informational message -}; -``` - ---- - -## Callbacks - -### Standard Callback Pattern - -All callbacks follow this pattern: - -```cpp -downstream.RegisterCallback(laneId, - [](const MessageType& data) { - // Handle message - } -); -``` - -### Available Callbacks - -#### Downstream Callbacks - -```cpp -// Board available from upstream -RegisterCallback(laneId, callback); - -// Board forecast notification -RegisterCallback(laneId, callback); - -// Transport finished -RegisterCallback(laneId, callback); - -// Notifications from upstream -RegisterCallback(laneId, callback); - -// Connection state -RegisterConnectionCallbacks(laneId, onConnect, onDisconnect); -``` - -#### Upstream Callbacks - -```cpp -// Machine ready signal from downstream -RegisterCallback(laneId, callback); - -// Start/stop transport signals -RegisterCallback(laneId, callback); -RegisterCallback(laneId, callback); - -// Notifications from downstream -RegisterCallback(laneId, callback); - -// Connection state -RegisterConnectionCallbacks(laneId, onConnect, onDisconnect); -``` - -#### Configuration Service Callbacks - -```cpp -// Configuration requests -RegisterCallback(laneId, callback); -RegisterCallback(laneId, callback); - -// Work order queries -RegisterCallback(laneId, callback); -RegisterCallback(laneId, callback); - -// Board info requests -RegisterCallback(laneId, callback); -``` - ---- - -## Complete Example - -```cpp -#include -#include -#include - -class SMTMachine { -private: - Hermes::Upstream upstream_; - Hermes::Downstream downstream_; - -public: - void Initialize() { - SetupDownstream(); - SetupUpstream(); - } - - void SetupDownstream() { - // Configure receiving from previous machine - Hermes::DownstreamSettings settings; - settings._laneId = 1; - settings._machineId = "MACHINE-02"; - settings._port = 50101; - - // Register callbacks BEFORE enabling - downstream_.RegisterCallback(1, - std::bind(&SMTMachine::OnBoardAvailable, this, - std::placeholders::_1) - ); - - downstream_.RegisterCallback(1, - std::bind(&SMTMachine::OnNotification, this, - std::placeholders::_1) - ); - - downstream_.RegisterConnectionCallbacks(1, - []() { std::cout << "Upstream connected" << std::endl; }, - []() { std::cout << "Upstream disconnected" << std::endl; } - ); - - // Start listening - downstream_.Enable(1, settings); - } - - void SetupUpstream() { - // Configure sending to next machine - Hermes::UpstreamSettings settings; - settings._laneId = 1; - settings._machineId = "MACHINE-02"; - settings._hostAddress = "192.168.1.30"; - settings._port = 50101; - - upstream_.Connect(1, settings); - } - - void OnBoardAvailable(const Hermes::BoardAvailableData& board) { - std::cout << "=== Board Arrived ===" << std::endl; - std::cout << "ID: " << board._boardId << std::endl; - std::cout << "Size: " << board._lengthInMM << "x" - << board._widthInMM << "mm" << std::endl; - - // Signal ready to receive - Hermes::MachineReadyData ready; - ready._failedBoard = Hermes::EBoardQuality::eGOOD; - downstream_.Signal(1, ready); - - // Process board - ProcessBoard(board); - - // Forward to next machine - upstream_.Signal(1, board); - - // Confirm transport finished - Hermes::TransportFinishedData finished; - finished._transferState = Hermes::ETransferState::eCOMPLETE; - finished._boardId = board._boardId; - upstream_.Signal(1, finished); - } - - void OnNotification(const Hermes::NotificationData& notification) { - std::cout << "Notification [" << static_cast(notification._severity) - << "]: " << notification._description << std::endl; - } - - void ProcessBoard(const Hermes::BoardAvailableData& board) { - // Simulate processing time - std::cout << "Processing board..." << std::endl; - std::this_thread::sleep_for(std::chrono::seconds(5)); - std::cout << "Processing complete" << std::endl; - } - - void Shutdown() { - downstream_.Disable(1); - upstream_.Disconnect(1); - } -}; - -int main() { - SMTMachine machine; - machine.Initialize(); - - // Run for 1 hour - std::this_thread::sleep_for(std::chrono::hours(1)); - - machine.Shutdown(); - return 0; -} -``` - ---- - -## Error Handling - -The library uses exceptions for error conditions: - -```cpp -try { - upstream.Connect(1, settings); -} catch (const std::exception& e) { - std::cerr << "Connection failed: " << e.what() << std::endl; -} -``` - -Common exceptions: -- Network connection failures -- Invalid configuration -- Protocol violations -- Timeout errors - ---- - -## Thread Safety - -- Each lane operates independently and is thread-safe -- Callbacks are invoked on internal thread - keep handlers quick -- For long operations, dispatch to worker thread from callback - -```cpp -downstream.RegisterCallback(1, - [this](const BoardAvailableData& board) { - // Quick handling in callback - std::thread([this, board]() { - // Long operation in separate thread - ProcessBoard(board); - }).detach(); - } -); -``` - ---- - -**See also:** -- [Getting Started Guide](GETTING_STARTED.md) -- [Examples](EXAMPLES.md) -- [IPC-HERMES-9852 Standard](https://www.the-hermes-standard.info/) \ No newline at end of file diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 668bd23..36d59b1 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -1,246 +1,100 @@ -# Building Hermes C++ Library +# Building the Hermes C++ Library -Comprehensive build instructions for all supported platforms. +The Hermes library uses **CMake** as its universal build system. The library has been modernized to use **Header-Only Boost** for its core networking, making it incredibly lightweight and easy to compile natively on Windows, Linux, and macOS without complex binary linking. -## Table of Contents +--- -- [System Requirements](#system-requirements) -- [Dependencies](#dependencies) -- [Windows Build](#windows-build) -- [MinGW/MSYS2](#mingwmsys2) -- [Visual Studio](#visual-studio) -- [Linux Build](#linux-build) -- [Test Suite](#test-suite) -- [Troubleshooting](#troubleshooting) +## 1. Prerequisites -## System Requirements +Before building, ensure your environment has the following installed: -- **C++17 compatible compiler** -- **500 MB disk space** for source + dependencies +* **C++17 Compiler**: + * **Linux**: GCC 7+ or Clang 5+ + * **Windows**: MSYS2/MinGW-w64 (GCC) or MSVC (Visual Studio 2017+) + * **macOS**: Apple Clang +* **CMake**: Version 3.15 or higher. +* **Boost Libraries**: Version 1.66 or newer. *(Note: The core library only requires Boost headers. No compiled `.lib` or `.so` Boost binaries are required unless you are building the Test Suite).* +* **pugixml**: Included locally in the `References/` directory, so no external installation is required. -## Dependencies +--- -### Required Dependencies +## 2. Environment Setup (From Scratch) -#### Boost (1.66-1.78) +Depending on your operating system, follow the steps below to ensure you have all required build tools present before running CMake. -**⚠️ IMPORTANT**: Hermes uses Boost ASIO with `io_service`, which was removed in Boost 1.87+. You MUST use Boost 1.66-1.78. - -**Windows (MinGW):** -```batch -# Download Boost 1.78: -https://archives.boost.io/release/1.78.0/source/boost_1_78_0.zip - -# Extract and copy boost/ folder to: -References\boost\ -``` - -**Linux:** +### 🐧 Linux (Ubuntu / Debian) +Linux is the easiest platform to build on. Simply install the standard build essentials and Boost headers from your package manager: ```bash -# Ubuntu/Debian -sudo apt-get install libboost-dev libboost-system-dev libboost-thread-dev +sudo apt update && sudo apt upgrade -y +sudo apt install -y g++ cmake libboost-all-dev git +🪟 Windows (via MSYS2 / MinGW - Recommended) +To build a lightweight .dll using GCC on Windows, use MSYS2 (UCRT64 environment). Open your MSYS2 terminal and install the toolchain: -# Fedora/RHEL -sudo dnf install boost-devel +Bash +pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-make mingw-w64-ucrt-x86_64-boost +Crucial: If you build from a standard Windows Command Prompt (cmd) instead of the MSYS2 terminal, you must temporarily add the MSYS2 binaries to your path before running CMake: -# Arch -sudo pacman -S boost +DOS +set PATH=C:\msys64\ucrt64\bin;%PATH% +🪟 Windows (via Visual Studio / MSVC) +If you prefer Microsoft Visual Studio, ensure the "Desktop development with C++" workload is installed. You will also need to download Boost headers (e.g., from boost.org) and extract them to a local directory (e.g., C:\local\boost_1_84_0). -``` +3. Standard Build Process +The CMake workflow is generally identical regardless of your operating system. Open a terminal in the root of the cloned repository. -#### Pugixml +Step 1: Create a Build Directory +Always perform an "out-of-source" build to keep your source tree clean. -**Windows (MinGW):** -```batch -# Download from: -https://github.com/zeux/pugixml/releases/latest +Bash +mkdir build +cd build +Step 2: Configure the Project +Run CMake to generate the build files. -# Extract and copy src/*.cpp and src/*.hppto: -References\pugixml\ -``` +For Linux / macOS: -**Linux:** -```bash -# Ubuntu/Debian -sudo apt-get install libpugixml-dev +Bash +cmake .. +make -j4 +For Windows (MSYS2 / MinGW): -# Fedora/RHEL -sudo dnf install pugixml-devel +DOS +cmake .. -G "MinGW Makefiles" +For Windows (Visual Studio): -# Arch -sudo pacman -S pugixml +DOS +cmake .. -DBOOST_ROOT="C:/local/boost_1_84_0" +Step 3: Compile the Library +Execute the build command to compile the library. -``` +Bash +cmake --build . --config Release +Build Outputs +Once the build completes successfully, the compiled binaries will be located in the build/src/Hermes/ directory: -## Windows Build +Windows: hermes.dll -### MinGW/MSYS2 +Linux: libhermes.so -#### 1. Install MSYS2 +macOS: libhermes.dylib -Download and install from: https://www.msys2.org/ +4. Building and Running the Tests +The repository includes a test suite (BoostTestHermes) to verify protocol compliance. -#### 2. Install Build Tools +Note: While the core library is header-only, building the tests requires the compiled boost_unit_test_framework binary. -Open MSYS2 MinGW 64-bit terminal: -```bash -pacman -Syu -pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-make -``` - -#### 3. Set Up Dependencies - -Follow the [Dependencies](#dependencies) section to install Boost and Pugixml. - -Directory structure should be: -``` -lib_cpp/ -├── References/ -│ ├── boost/ -│ │ ├── asio.hpp -│ │ ├── system/ -│ │ └── ... (all boost headers) -│ └── pugixml/ -│ ├── pugixml.hpp -│ ├── pugiconfig.hpp -│ └── pugixml.cpp -``` - -#### 4. Build -```batch -# Run the build script: -setup_windows.bat - -# Or build manually: -cd src\Hermes -g++ -std=c++17 -O2 -Wall -DHERMES_EXPORTS ^ - -I..\include -I..\..\References -I..\..\References\pugixml ^ - -c *.cpp - -g++ -shared -o Hermes.dll *.o Hermes.def ^ - -lws2_32 -lmswsock -liphlpapi ^ - -static-libgcc -static-libstdc++ -``` - -### Visual Studio - -#### 1. Install Visual Studio - -- Visual Studio -- MSVC v143 C++ Build Tools -- C++ Desktop Development workload - -#### 2. Set Up Dependencies - -Create directory structure: -``` -lib_cpp/ -├── References/ -│ ├── boost/ # Boost headers -│ ├── pugixml/ # Pugixml headers and source -│ ├── lib32/ # 32-bit Boost libraries (optional) -│ └── lib64/ # 64-bit Boost libraries (optional) -``` - -#### 3. Build - -1. Open `Hermes.sln` in Visual Studio -2. Select configuration (Debug/Release) -3. Select platform (Win32/x64) -4. Build Solution (Ctrl+Shift+B) - -Output will be in: -``` -bin\$(Platform)\$(Configuration)\Hermes.dll -``` - - -## Linux Build - -### 1. Install Dependencies - -**Ubuntu/Debian:** -```bash -sudo apt-get update -sudo apt-get install build-essential g++ libboost-all-dev libpugixml-dev -``` +To run the tests from inside your build directory: -**Fedora/RHEL:** -```bash -sudo dnf install gcc-c++ boost-devel pugixml-devel -``` +Bash +ctest --output-on-failure -C Release +Alternatively, you can run the executable directly: -**Arch:** -```bash -sudo pacman -S base-devel boost pugixml -``` +Windows: .\test\BoostTestHermes\Release\BoostTestHermes.exe -### 2. Build -```bash -# Run build script: -chmod +x setup.sh -./setup.sh - -# Or build manually: -cd src/Hermes -g++ -fPIC -std=c++17 -O2 \ - -DBOOST_ERROR_CODE_HEADER_ONLY=0 \ - -DBOOST_SYSTEM_NO_DEPRECATED \ - -I../include \ - -c *.cpp - -g++ -shared -Wl,-soname,libhermes.so.3 \ - -o libhermes.so.3.1.0 *.o \ - -lboost_system -lboost_thread -lpugixml -lpthread - -ln -sf libhermes.so.3.1.0 libhermes.so -``` - -## Test Suite - -**Simple Test:** - -### Simple Test (No Boost.Test Required) - -Create `simple_test.cpp`: -```cpp -#include -#include "Hermes.h" - -int main() { - std::cout << "Testing Hermes library..." << std::endl; - - // Create minimal downstream - HermesDownstreamCallbacks callbacks = {}; - HermesDownstream* p = CreateHermesDownstream(1, &callbacks); - - if (p) { - std::cout << "SUCCESS: Library loaded and functional" << std::endl; - DeleteHermesDownstream(p); - return 0; - } - - std::cerr << "FAILED: Could not create Hermes object" << std::endl; - return 1; -} -`` -no Boost compilation needed. -```batch -# Build and run simple test: -g++ -std=c++17 -I src/include -L . ^ - -o simple_test.exe simple_test.cpp ^ - -lHermes -lws2_32 -lmswsock ^ - -static-libgcc -static-libstdc++ - -simple_test.exe -``` - -You can use the test.cpp for testing every fucntion. - - -**Official Test Suite (Advanced):** -Download pre-compiled Boost libraries from: -https://sourceforge.net/projects/boost/files/boost-binaries/ - -Building Boost from source is not required. -Run Makefile in test\BoostTestHermes +Linux: ./test/BoostTestHermes/BoostTestHermes + +To compile any file run +g++ -std=c++17 -o example.exe example.cpp \ + -I~/lib_cpp/src/include \ + -L~/lib_cpp/build -lhermes \ + -lboost_system -lpthread \ No newline at end of file diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md deleted file mode 100644 index 682b4df..0000000 --- a/docs/GETTING_STARTED.md +++ /dev/null @@ -1,509 +0,0 @@ -# Getting Started with Hermes - -This guide will walk you through using the Hermes library in your SMT application. - ---- - -## Table of Contents - -1. [Installation](#installation) -2. [First Application](#first-application) -3. [Understanding Hermes Roles](#understanding-hermes-roles) -4. [Common Patterns](#common-patterns) -5. [Troubleshooting](#troubleshooting) - ---- - -## Installation - -### Step 1: Install Dependencies - -**Windows (MinGW):** -```batch -# Download Boost 1.78.0 -# https://archives.boost.io/release/1.78.0/source/boost_1_78_0.zip -# Extract and copy boost/ folder to: References\boost\ - -# Download Pugixml -# https://github.com/zeux/pugixml/releases -# Copy src/*.hpp and src/*.cpp to: References\pugixml\ - -# Verify MinGW installed -where g++ -where mingw32-make -``` - -**Linux (Ubuntu/Debian):** -```bash -# Install from package manager -sudo apt-get update -sudo apt-get install build-essential libboost-all-dev libpugixml-dev - -# Verify installation -g++ --version -``` - -### Step 2: Build Hermes Library - -**Windows:** -```batch -cd hermes -setup_windows.bat -``` - -**Linux:** -```bash -cd hermes -chmod +x setup.sh -./setup.sh -``` - -### Step 3: Verify Installation - -You should see: -``` -[OK] Boost found -[OK] Pugixml found -[BUILD] Compiling Hermes library... -[OK] Hermes.dll created -[TEST] Running tests... -[SUCCESS] All tests passed -``` - ---- - -## First Application - -### Hello Hermes - Minimal Connection Test - -Create `hello_hermes.cpp`: - -```cpp -#include -#include -#include -#include - -int main() { - std::cout << "=== Hermes Connection Test ===" << std::endl; - - // Create an upstream connection object - Hermes::Upstream upstream; - - // Configure connection parameters - Hermes::UpstreamSettings settings; - settings._laneId = 1; // Lane/track ID - settings._hostAddress = "192.168.1.10"; // Downstream machine IP - settings._port = 50101; // Hermes default port - - std::cout << "Connecting to " << settings._hostAddress - << ":" << settings._port << std::endl; - - // Attempt connection - upstream.Connect(1, settings); - - // Wait for connection to establish - std::this_thread::sleep_for(std::chrono::seconds(2)); - - std::cout << "Connection established!" << std::endl; - - // Send machine ready notification - Hermes::NotificationData notification; - notification._notificationCode = Hermes::ENotificationCode::eMACHINE_READY; - notification._severity = Hermes::ESeverity::eINFO; - notification._description = "Machine initialized"; - - upstream.Signal(1, notification); - std::cout << "Sent MACHINE_READY notification" << std::endl; - - // Disconnect gracefully - upstream.Disconnect(1); - std::cout << "Disconnected" << std::endl; - - return 0; -} -``` - -### Compile and Run - -**Windows:** -```batch -g++ -std=c++17 -I src/include -L . -o hello_hermes.exe hello_hermes.cpp ^ - -lHermes -lws2_32 -lmswsock -static-libgcc -static-libstdc++ - -hello_hermes.exe -``` - -**Linux:** -```bash -g++ -std=c++17 -I src/include -L . -o hello_hermes hello_hermes.cpp \ - -lhermes -lboost_system -lboost_thread -lpugixml -lpthread \ - -Wl,-rpath,'$ORIGIN' - -./hello_hermes -``` - ---- - -## Understanding Hermes Roles - -Hermes defines three main communication roles: - -### 1. Upstream (Sending Machine) - -A machine that sends boards **downstream** to the next machine. - -```cpp -Hermes::Upstream upstream; - -// Connect to next machine -UpstreamSettings settings; -settings._hostAddress = "192.168.1.20"; // Next machine IP -settings._port = 50101; - -upstream.Connect(1, settings); - -// Signal board available -BoardAvailableData board; -board._boardId = "PCB-001"; -board._topBarcode = "BARCODE-TOP"; -board._lengthInMM = 250.0; -board._widthInMM = 200.0; - -upstream.Signal(1, board); -``` - -**Use Case:** Loader, printer, placement machine sending to next station - -### 2. Downstream (Receiving Machine) - -A machine that receives boards from the **upstream** machine. - -```cpp -Hermes::Downstream downstream; - -// Listen for incoming boards -DownstreamSettings settings; -settings._port = 50101; // Listen on this port - -downstream.Enable(1, settings); - -// Register callback for board arrival -downstream.RegisterCallback(1, - [](const BoardAvailableData& board) { - std::cout << "Received board: " << board._boardId << std::endl; - } -); -``` - -**Use Case:** Placement machine, reflow oven, AOI receiving from previous station - -### 3. Bidirectional (Middle Machine) - -Most machines in a line are both upstream AND downstream. - -```cpp -// Receive from previous machine -Hermes::Downstream downstream; -downstream.Enable(1, downstreamSettings); - -// Send to next machine -Hermes::Upstream upstream; -upstream.Connect(1, upstreamSettings); - -// Process board and pass it along -downstream.RegisterCallback(1, - [&upstream](const BoardAvailableData& board) { - // Process board here... - - // Send to next machine - upstream.Signal(1, board); - } -); -``` - -**Use Case:** Pick-and-place, inspection, coating machines in middle of line - ---- - -## Common Patterns - -### Pattern 1: Board Flow (Complete Transaction) - -```cpp -#include -#include - -class HermesMachine { -private: - Hermes::Upstream upstream_; - Hermes::Downstream downstream_; - -public: - void Initialize() { - // Setup downstream (receive from previous) - DownstreamSettings downSettings; - downSettings._port = 50101; - downstream_.Enable(1, downSettings); - - // Setup upstream (send to next) - UpstreamSettings upSettings; - upSettings._hostAddress = "192.168.1.20"; - upSettings._port = 50101; - upstream_.Connect(1, upSettings); - - // Register board arrival handler - downstream_.RegisterCallback(1, - std::bind(&HermesMachine::OnBoardArrived, this, - std::placeholders::_1) - ); - } - - void OnBoardArrived(const BoardAvailableData& board) { - std::cout << "Board arrived: " << board._boardId << std::endl; - - // Signal ready to receive - MachineReadyData ready; - ready._failedBoard = Hermes::EBoardQuality::eGOOD; - downstream_.Signal(1, ready); - - // Simulate processing - ProcessBoard(board); - - // Send to next machine - upstream_.Signal(1, board); - - // Signal transport finished - TransportFinishedData finished; - finished._transferState = Hermes::ETransferState::eCOMPLETE; - upstream_.Signal(1, finished); - } - - void ProcessBoard(const BoardAvailableData& board) { - // Your machine logic here - std::cout << "Processing board..." << std::endl; - } -}; - -int main() { - HermesMachine machine; - machine.Initialize(); - - // Keep running - std::this_thread::sleep_for(std::chrono::hours(24)); - return 0; -} -``` - -### Pattern 2: Error Handling and Notifications - -```cpp -void SendErrorNotification(Hermes::Upstream& upstream, - const std::string& error) { - NotificationData notification; - notification._notificationCode = - Hermes::ENotificationCode::eMACHINE_ERROR; - notification._severity = Hermes::ESeverity::eERROR; - notification._description = error; - - upstream.Signal(1, notification); -} - -void HandleBoardDefect(Hermes::Upstream& upstream, - const BoardAvailableData& board) { - // Mark board as failed - board._failedBoard = Hermes::EBoardQuality::eFAILED; - - // Send defect notification - NotificationData notification; - notification._notificationCode = - Hermes::ENotificationCode::eBOARD_DEFECT; - notification._severity = Hermes::ESeverity::eWARNING; - notification._description = "Board quality check failed"; - - upstream.Signal(1, notification); - upstream.Signal(1, board); -} -``` - -### Pattern 3: Work Order Management - -```cpp -void RequestWorkOrder(Hermes::ConfigurationService& config) { - GetWorkOrderDataRequest request; - request._workOrderId = "WO-12345"; - - config.Signal(1, request); -} - -void SetWorkOrder(Hermes::ConfigurationService& config, - const std::string& workOrderId) { - SetWorkOrderDataRequest request; - request._workOrderId = workOrderId; - - WorkOrderData workOrder; - workOrder._workOrderIdentifier = workOrderId; - workOrder._boardIdCreatedFrom = "TEMPLATE-001"; - workOrder._productTypeId = "PRODUCT-ABC"; - - request._workOrder = workOrder; - config.Signal(1, request); -} -``` - -### Pattern 4: Callbacks and Events - -```cpp -class MyMachine { -public: - void SetupCallbacks(Hermes::Downstream& downstream) { - // Board available from upstream - downstream.RegisterBoardAvailableCallback(1, - [this](const BoardAvailableData& data) { - OnBoardAvailable(data); - } - ); - - // Board forecast (advance notice) - downstream.RegisterBoardForecastCallback(1, - [this](const BoardForecastData& data) { - OnBoardForecast(data); - } - ); - - // Connection state changes - downstream.RegisterConnectedCallback(1, - [this]() { - std::cout << "Upstream machine connected" << std::endl; - } - ); - - downstream.RegisterDisconnectedCallback(1, - [this]() { - std::cout << "Upstream machine disconnected" << std::endl; - } - ); - } - -private: - void OnBoardAvailable(const BoardAvailableData& board) { - std::cout << "Board: " << board._boardId - << " (" << board._lengthInMM << "x" - << board._widthInMM << "mm)" << std::endl; - } - - void OnBoardForecast(const BoardForecastData& forecast) { - std::cout << "Forecast: " << forecast._boardId - << " arriving in " << forecast._timeUntilAvailable - << "s" << std::endl; - } -}; -``` - ---- - -## Troubleshooting - -### Connection Issues - -**Problem:** `Connect()` fails or times out - -**Solutions:** -```cpp -// 1. Verify network connectivity -ping 192.168.1.20 - -// 2. Check port is not blocked -telnet 192.168.1.20 50101 - -// 3. Verify other machine is listening -// (downstream must Enable() before upstream Connect()) - -// 4. Check firewall settings -# Windows: Allow Hermes.dll through Windows Firewall -# Linux: sudo ufw allow 50101/tcp -``` - -### Compilation Errors - -**Problem:** `undefined reference to boost::asio::io_service` - -**Solution:** Wrong Boost version! Use 1.66-1.78: -```bash -# Download correct version -wget https://archives.boost.io/release/1.78.0/source/boost_1_78_0.zip -``` - -**Problem:** `cannot find -lHermes` - -**Solution:** Ensure `Hermes.dll` is in current directory or library path: -```batch -# Windows -dir Hermes.dll - -# Linux -ls -l libhermes.so* -export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH -``` - -### Runtime Errors - -**Problem:** Application crashes with `DLL not found` - -**Solution:** Copy `Hermes.dll` to executable directory: -```batch -copy Hermes.dll Release\ -``` - -**Problem:** No data received from upstream - -**Solution:** Verify callback registration happens BEFORE `Enable()`: -```cpp -// WRONG order -downstream.Enable(1, settings); -downstream.RegisterCallback(...); // Too late! - -// CORRECT order -downstream.RegisterCallback(...); -downstream.Enable(1, settings); -``` - ---- - -## Next Steps - -1. **Read API Reference:** [API_REFERENCE.md](API_REFERENCE.md) -2. **Study Examples:** [EXAMPLES.md](EXAMPLES.md) -3. **Review Test Code:** `test/BoostTestHermes/*.cpp` -4. **IPC Standard:** [Download IPC-HERMES-9852](https://www.the-hermes-standard.info/) - ---- - -## Quick Reference Card - -```cpp -// UPSTREAM (Sending boards downstream) -Hermes::Upstream up; -up.Connect(laneId, settings); -up.Signal(laneId, boardData); - -// DOWNSTREAM (Receiving boards from upstream) -Hermes::Downstream down; -down.Enable(laneId, settings); -down.RegisterCallback(laneId, callback); - -// CONFIGURATION SERVICE -Hermes::ConfigurationService config; -config.Enable(laneId, settings); -config.Signal(laneId, request); - -// COMMON DATA STRUCTURES -BoardAvailableData board; -NotificationData notification; -MachineReadyData ready; -TransportFinishedData finished; -``` - ---- - -**Need help?** Check [examples/](../examples/) or open an issue on GitHub. \ No newline at end of file diff --git a/examples/interactive_sender.cpp b/examples/interactive_sender.cpp new file mode 100644 index 0000000..5e48413 --- /dev/null +++ b/examples/interactive_sender.cpp @@ -0,0 +1,75 @@ +#include +#include +#include "HermesModern.hpp" + +void PrintMenu() { + std::cout << "\n=========================================\n"; + std::cout << " INTERACTIVE HERMES SENDER CONSOLE\n"; + std::cout << "=========================================\n"; + std::cout << " Type a command and press Enter:\n"; + std::cout << " service -> Send ServiceDescription\n"; + std::cout << " board -> Send BoardAvailable (e.g., board PCB-123)\n"; + std::cout << " start -> Send TransportFinished (e.g., start PCB-123)\n"; + std::cout << " exit -> Shut down and close\n"; + std::cout << "=========================================\n"; +} + +int main() { + // Senders face DOWNSTREAM and act as TCP Servers + Hermes::Modern::Downstream sender(1); + + sender.RegisterConnectedCallback([&](const Hermes::ConnectionInfo& info) { + std::cout << "\n[NETWORK] Connected to Receiver at " << info.m_address << "!\n> "; + }); + + sender.RegisterMachineReadyCallback([&](const Hermes::MachineReadyData& data) { + std::cout << "\n[RECEIVER SAYS] I am MachineReady! Send me a board.\n> "; + }); + + sender.RegisterStartTransportCallback([&](const Hermes::StartTransportData& data) { + std::cout << "\n[RECEIVER SAYS] StartTransport for " << data.m_boardId << "! My conveyors are running.\n> "; + }); + + sender.RegisterStopTransportCallback([&](const Hermes::StopTransportData& data) { + std::cout << "\n[RECEIVER SAYS] StopTransport for " << data.m_boardId << ". I have the board completely.\n> "; + }); + + std::cout << "Opening Server on port 50101...\n"; + sender.Enable(Hermes::DownstreamSettings("Interactive_Sender", 50101)); + + PrintMenu(); + std::string command; + + while (true) { + std::cout << "> "; + std::cin >> command; + + if (command == "exit") { + break; + } + else if (command == "service") { + sender.Signal(1, Hermes::ServiceDescriptionData("Interactive_Sender", 1)); + std::cout << "[SENT] ServiceDescriptionData\n"; + } + else if (command == "board") { + std::string boardId; + std::cin >> boardId; + sender.Signal(1, Hermes::BoardAvailableData(boardId, "Interactive_Sender", Hermes::EBoardQuality::eGOOD, Hermes::EFlippedBoard::eTOP_SIDE_IS_UP)); + std::cout << "[SENT] BoardAvailableData for " << boardId << "\n"; + } + else if (command == "start") { + std::string boardId; + std::cin >> boardId; + sender.Signal(1, Hermes::TransportFinishedData(Hermes::ETransferState::eCOMPLETE, boardId)); + std::cout << "[SENT] TransportFinishedData for " << boardId << "\n"; + } + else { + std::cout << "[ERROR] Unknown command.\n"; + std::cin.clear(); + std::cin.ignore(10000, '\n'); + } + } + + sender.Stop(); + return 0; +} diff --git a/examples/machine_b_pnp.cpp b/examples/machine_b_pnp.cpp new file mode 100644 index 0000000..995c848 --- /dev/null +++ b/examples/machine_b_pnp.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include "HermesModern.hpp" + +int main() { + std::cout << "========================================\n"; + std::cout << " [MACHINE B] - PICK & PLACE\n"; + std::cout << " Connecting to 50101 (A) | Listening on 50102 (C)\n"; + std::cout << "========================================\n\n"; + + // Receiver connects UPSTREAM to A. Sender listens DOWNSTREAM for C. + Hermes::Modern::Upstream receiver(1); + Hermes::Modern::Downstream sender(1); + + // --- RECEIVING FROM MACHINE A (Upstream) --- + receiver.RegisterServiceDescriptionCallback([&](const Hermes::ServiceDescriptionData& data) { + std::cout << "[B] Upstream " << data.m_machineId << " connected. I am ready.\n"; + receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); + }); + + receiver.RegisterBoardAvailableCallback([&](const Hermes::BoardAvailableData& board) { + std::cout << "[B] Board " << board.m_boardId << " waiting at upstream edge. Pulling...\n"; + receiver.Signal(1, Hermes::StartTransportData(board.m_boardId)); + }); + + receiver.RegisterTransportFinishedCallback([&](const Hermes::TransportFinishedData& data) { + receiver.Signal(1, Hermes::StopTransportData(Hermes::ETransferState::eCOMPLETE, data.m_boardId)); + std::cout << "[B] Board " << data.m_boardId << " received. Placing components...\n"; + + std::this_thread::sleep_for(std::chrono::seconds(3)); + std::cout << "[B] Placement done! Passing to Machine C...\n"; + + sender.Signal(1, Hermes::BoardAvailableData(data.m_boardId, "Machine_B_PnP", Hermes::EBoardQuality::eGOOD, Hermes::EFlippedBoard::eTOP_SIDE_IS_UP)); + }); + + // --- SENDING TO MACHINE C (Downstream) --- + sender.RegisterConnectedCallback([&](const Hermes::ConnectionInfo& info) { + std::cout << "[B] Connected to Downstream at " << info.m_address << "\n"; + sender.Signal(1, Hermes::ServiceDescriptionData("Machine_B_PnP", 1)); + }); + + sender.RegisterMachineReadyCallback([&](const Hermes::MachineReadyData&) { + std::cout << "[B] Machine C is ready for boards.\n"; + }); + + sender.RegisterStartTransportCallback([&](const Hermes::StartTransportData& data) { + std::cout << "[B] Machine C is pulling. Running exit conveyors...\n"; + std::this_thread::sleep_for(std::chrono::seconds(1)); + + sender.Signal(1, Hermes::TransportFinishedData(Hermes::ETransferState::eCOMPLETE, data.m_boardId)); + std::cout << "[B] Board successfully handed off to Machine C.\n\n"; + + receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); + }); + + // Start Server first, then connect Client + sender.Enable(Hermes::DownstreamSettings("Machine_B_PnP", 50102)); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + receiver.Enable(Hermes::UpstreamSettings("Machine_B_PnP", "127.0.0.1", 50101)); + + std::cout << "Press Enter to shut down Machine B...\n\n"; + std::cin.get(); + receiver.Stop(); + sender.Stop(); + return 0; +} \ No newline at end of file diff --git a/examples/machine_c_oven.cpp b/examples/machine_c_oven.cpp new file mode 100644 index 0000000..8dba590 --- /dev/null +++ b/examples/machine_c_oven.cpp @@ -0,0 +1,41 @@ +#include +#include +#include "HermesModern.hpp" + +int main() { + std::cout << "========================================\n"; + std::cout << " [MACHINE C] - REFLOW OVEN (Receiver)\n"; + std::cout << " Connecting to Port 50102...\n"; + std::cout << "========================================\n\n"; + + // Receivers face UPSTREAM to connect to the previous machine + Hermes::Modern::Upstream receiver(1); + + receiver.RegisterServiceDescriptionCallback([&](const Hermes::ServiceDescriptionData& data) { + std::cout << "[C] Connected to " << data.m_machineId << ". Ready for boards.\n"; + receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); + }); + + receiver.RegisterBoardAvailableCallback([&](const Hermes::BoardAvailableData& board) { + std::cout << "[C] Board detected at entrance: " << board.m_boardId << "\n"; + std::cout << "[C] Starting conveyors to pull it in...\n"; + receiver.Signal(1, Hermes::StartTransportData(board.m_boardId)); + }); + + receiver.RegisterTransportFinishedCallback([&](const Hermes::TransportFinishedData& data) { + receiver.Signal(1, Hermes::StopTransportData(Hermes::ETransferState::eCOMPLETE, data.m_boardId)); + std::cout << "[C] Board " << data.m_boardId << " is fully inside!\n"; + std::cout << "[C] Baking... Handover cycle complete.\n\n"; + + receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); + }); + + // Client connects to the Sender + Hermes::UpstreamSettings settings("Machine_C_Oven", "127.0.0.1", 50102); + receiver.Enable(settings); + + std::cout << "Press Enter to shut down Machine C...\n\n"; + std::cin.get(); + receiver.Stop(); + return 0; +} \ No newline at end of file diff --git a/examples/rpi1_upstream.cpp b/examples/rpi1_upstream.cpp new file mode 100644 index 0000000..960514d --- /dev/null +++ b/examples/rpi1_upstream.cpp @@ -0,0 +1,155 @@ +// ============================================================================= +// rpi1_upstream.cpp — RPi1: Upstream machine +// +// Build: +// g++ -std=c++17 -o rpi1_upstream rpi1_upstream.cpp \ +// -I/path/to/hermes/src/include \ +// -L/path/to/hermes/build -lhermes \ +// -lboost_system -lpthread +// +// Run: +// LD_LIBRARY_PATH=/path/to/hermes/build ./rpi1_upstream +// +// What it does: +// - Connects to RPi2 (Downstream) on port 50100 +// - Sends ServiceDescription to identify itself +// - Waits for ServiceDescription back from RPi2 +// - Prints connection state changes to console +// ============================================================================= + +#include "Hermes.hpp" +#include +#include +#include +#include +#include +#include + +// ----------------------------------------------------------------------- +// EDIT THIS: set RPi2's IP address here +// On RPi2, run: hostname -I +// ----------------------------------------------------------------------- +static const std::string RPI2_IP = "192.168.1.102"; // <-- CHANGE THIS +static const uint16_t HERMES_PORT = 50100; +static const std::string THIS_MACHINE_ID = "RPi1-Upstream"; + +static std::atomic g_running{true}; + +void signalHandler(int) { g_running = false; } + +// ----------------------------------------------------------------------- +// Upstream callback — receives messages FROM the Downstream (RPi2) +// ----------------------------------------------------------------------- +struct UpstreamCallback : Hermes::IUpstreamCallback +{ + void OnConnected(unsigned sessionId, Hermes::EState state, + const Hermes::ConnectionInfo& info) override + { + std::cout << "[RPi1] Connected to RPi2" + << " address=" << info.m_address + << " port=" << info.m_port + << " session=" << sessionId + << "\n"; + } + + // RPi2 sends its ServiceDescription — we receive it here + void On(unsigned sessionId, Hermes::EState state, + const Hermes::ServiceDescriptionData& data) override + { + std::cout << "[RPi1] Received ServiceDescription from RPi2" + << " machineId=" << data.m_machineId + << " laneId=" << data.m_laneId + << "\n"; + std::cout << "[RPi1] ** Hermes connection established successfully **\n"; + } + + // These are required by the interface but not relevant for this demo + void On(unsigned, Hermes::EState, const Hermes::BoardAvailableData&) override {} + void On(unsigned, Hermes::EState, const Hermes::RevokeBoardAvailableData&) override {} + void On(unsigned, Hermes::EState, const Hermes::TransportFinishedData&) override {} + + void On(unsigned sessionId, const Hermes::NotificationData& data) override + { + std::cout << "[RPi1] Notification from RPi2: " << data.m_description << "\n"; + } + + void On(unsigned, const Hermes::CheckAliveData&) override {} + + void On(unsigned sessionId, const Hermes::CommandData&) override {} + + void OnState(unsigned sessionId, Hermes::EState state) override + { + std::cout << "[RPi1] State changed: " << static_cast(state) << "\n"; + } + + void OnDisconnected(unsigned sessionId, Hermes::EState state, + const Hermes::Error& error) override + { + std::cout << "[RPi1] Disconnected from RPi2"; + if (error) + std::cout << " reason=" << error.m_text; + std::cout << "\n"; + } + + void OnTrace(unsigned, Hermes::ETraceType type, Hermes::StringView trace) override + { + // Uncomment to see full protocol trace: + // std::cout << "[RPi1][TRACE] " << std::string(trace) << "\n"; + } +}; + +int main() +{ + std::signal(SIGINT, signalHandler); + std::signal(SIGTERM, signalHandler); + + std::cout << "[RPi1] Starting Hermes Upstream\n"; + std::cout << "[RPi1] Connecting to RPi2 at " << RPI2_IP + << ":" << HERMES_PORT << "\n"; + + UpstreamCallback callback; + Hermes::Upstream upstream(1, callback); // lane 1 + + // Settings: who we are, and who we connect to + Hermes::UpstreamSettings settings; + settings.m_machineId = THIS_MACHINE_ID; + settings.m_hostAddress = RPI2_IP; + settings.m_port = HERMES_PORT; + settings.m_checkAlivePeriodInSeconds = 60.0; + settings.m_reconnectWaitTimeInSeconds = 5.0; + + // Enable runs the TCP connection + Hermes handshake in background + upstream.Enable(settings); + + // Run the Hermes event loop in a thread + std::thread networkThread([&upstream]() { + upstream.Run(); + }); + + // After connection, send our ServiceDescription to RPi2 + // We post it onto the Hermes thread after a short delay to let + // the connection establish first + std::this_thread::sleep_for(std::chrono::seconds(2)); + + upstream.Post([&upstream]() { + Hermes::ServiceDescriptionData desc; + desc.m_machineId = THIS_MACHINE_ID; + desc.m_laneId = 1; + // session 0 = send to whatever session is currently active + // The library fills in the real session id internally via Post + std::cout << "[RPi1] Sending ServiceDescription to RPi2\n"; + }); + + std::cout << "[RPi1] Running. Press Ctrl+C to stop.\n"; + + while (g_running) + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + std::cout << "[RPi1] Shutting down...\n"; + upstream.Stop(); + if (networkThread.joinable()) + networkThread.join(); + + std::cout << "[RPi1] Done.\n"; + return 0; +} diff --git a/examples/rpi2_downstream.cpp b/examples/rpi2_downstream.cpp new file mode 100644 index 0000000..0547f36 --- /dev/null +++ b/examples/rpi2_downstream.cpp @@ -0,0 +1,157 @@ +// ============================================================================= +// rpi2_downstream.cpp — RPi2: Downstream machine +// +// Build: +// g++ -std=c++17 -o rpi2_downstream rpi2_downstream.cpp \ +// -I/path/to/hermes/src/include \ +// -L/path/to/hermes/build -lhermes \ +// -lboost_system -lpthread +// +// Run: +// LD_LIBRARY_PATH=/path/to/hermes/build ./rpi2_downstream +// +// What it does: +// - Listens on port 50100 for an incoming connection from RPi1 +// - Receives ServiceDescription from RPi1 +// - Sends ServiceDescription back to RPi1 +// - Prints connection state changes to console +// ============================================================================= + +#include "Hermes.hpp" +#include +#include +#include +#include +#include +#include + +static const uint16_t HERMES_PORT = 50100; +static const std::string THIS_MACHINE_ID = "RPi2-Downstream"; + +static std::atomic g_running{true}; +static unsigned g_activeSession{0}; + +// Forward declare so callback can reference it +Hermes::Downstream* g_downstream = nullptr; + +void signalHandler(int) { g_running = false; } + +// ----------------------------------------------------------------------- +// Downstream callback — receives messages FROM the Upstream (RPi1) +// ----------------------------------------------------------------------- +struct DownstreamCallback : Hermes::IDownstreamCallback +{ + void OnConnected(unsigned sessionId, Hermes::EState state, + const Hermes::ConnectionInfo& info) override + { + g_activeSession = sessionId; + std::cout << "[RPi2] RPi1 connected" + << " address=" << info.m_address + << " port=" << info.m_port + << " session=" << sessionId + << "\n"; + } + + // RPi1 sends its ServiceDescription — we receive it here + void On(unsigned sessionId, Hermes::EState state, + const Hermes::ServiceDescriptionData& data) override + { + std::cout << "[RPi2] Received ServiceDescription from RPi1" + << " machineId=" << data.m_machineId + << " laneId=" << data.m_laneId + << "\n"; + + // Respond with our own ServiceDescription + if (g_downstream) + { + g_downstream->Post([sessionId]() { + Hermes::ServiceDescriptionData reply; + reply.m_machineId = THIS_MACHINE_ID; + reply.m_laneId = 1; + + std::cout << "[RPi2] Sending ServiceDescription back to RPi1\n"; + g_downstream->Signal(sessionId, reply); + std::cout << "[RPi2] ** Hermes connection established successfully **\n"; + }); + } + } + + // These are required by the interface — not used in this demo + void On(unsigned, Hermes::EState, const Hermes::MachineReadyData&) override {} + void On(unsigned, Hermes::EState, const Hermes::RevokeMachineReadyData&) override {} + void On(unsigned, Hermes::EState, const Hermes::StartTransportData&) override {} + void On(unsigned, Hermes::EState, const Hermes::StopTransportData&) override {} + + void On(unsigned sessionId, const Hermes::NotificationData& data) override + { + std::cout << "[RPi2] Notification from RPi1: " << data.m_description << "\n"; + } + + void On(unsigned, const Hermes::CheckAliveData&) override {} + + void On(unsigned, const Hermes::CommandData&) override {} + + void OnState(unsigned sessionId, Hermes::EState state) override + { + std::cout << "[RPi2] State changed: " << static_cast(state) << "\n"; + } + + void OnDisconnected(unsigned sessionId, Hermes::EState state, + const Hermes::Error& error) override + { + std::cout << "[RPi2] RPi1 disconnected"; + if (error) + std::cout << " reason=" << error.m_text; + std::cout << "\n"; + g_activeSession = 0; + } + + void OnTrace(unsigned, Hermes::ETraceType, Hermes::StringView) override + { + // Uncomment to see full protocol trace: + // std::cout << "[RPi2][TRACE] " << std::string(trace) << "\n"; + } +}; + +int main() +{ + std::signal(SIGINT, signalHandler); + std::signal(SIGTERM, signalHandler); + + std::cout << "[RPi2] Starting Hermes Downstream\n"; + std::cout << "[RPi2] Listening on port " << HERMES_PORT + << " for RPi1...\n"; + + DownstreamCallback callback; + Hermes::Downstream downstream(1, callback); // lane 1 + g_downstream = &downstream; + + // Settings: who we are, and which port to listen on + // m_optionalClientAddress is left empty = accept from any IP + Hermes::DownstreamSettings settings; + settings.m_machineId = THIS_MACHINE_ID; + settings.m_port = HERMES_PORT; + settings.m_checkAlivePeriodInSeconds = 60.0; + settings.m_reconnectWaitTimeInSeconds = 5.0; + + downstream.Enable(settings); + + // Run Hermes event loop in a background thread + std::thread networkThread([&downstream]() { + downstream.Run(); + }); + + std::cout << "[RPi2] Running. Press Ctrl+C to stop.\n"; + + while (g_running) + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + std::cout << "[RPi2] Shutting down...\n"; + downstream.Stop(); + if (networkThread.joinable()) + networkThread.join(); + + g_downstream = nullptr; + std::cout << "[RPi2] Done.\n"; + return 0; +} diff --git a/setup.sh b/setup.sh deleted file mode 100644 index 27ce709..0000000 --- a/setup.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env bash -set -e - -echo "========================================" -echo " Hermes Build System (Linux/macOS)" -echo "========================================" -echo "" - -# Check dependencies -check_dep() { - if command -v $1 &>/dev/null || pkg-config --exists $2 2>/dev/null; then - echo " [OK] $1" - return 0 - fi - echo " [ERROR] $1 not found" - return 1 -} - -echo "[1/3] Checking dependencies..." -check_dep g++ gcc || exit 1 -check_dep pkg-config || exit 1 -check_dep boost || echo " [WARN] Boost not via pkg-config, checking headers..." -check_dep pugixml || echo " [WARN] Pugixml not via pkg-config, checking headers..." - -# Build library -echo "" -echo "[2/3] Building Hermes library..." -cd src/Hermes -make clean -make -cp -f .libs/libhermes.so* ../../ -cd ../.. - -# Build and run tests -echo "" -echo "[3/3] Running tests..." - -if [ -f "simple_test.cpp" ]; then - echo " Building simple_test..." - g++ -std=c++17 -I src/include -L . -o simple_test simple_test.cpp \ - -lhermes -lboost_system -lboost_thread -lpugixml -lpthread \ - -Wl,-rpath,'$ORIGIN' - ./simple_test || true -fi - -if [ -d "test/BoostTestHermes" ]; then - echo " Building official test suite..." - cd test/BoostTestHermes - make clean - make - ./BoostTestHermes || true - cd ../.. -fi - -echo "" -echo "========================================" -echo " Build Complete!" -echo "========================================" \ No newline at end of file diff --git a/setup_windows.bat b/setup_windows.bat deleted file mode 100644 index c801402..0000000 --- a/setup_windows.bat +++ /dev/null @@ -1,119 +0,0 @@ -@echo off -setlocal enabledelayedexpansion - -echo ======================================== -echo Hermes Build System (Windows/MinGW) -echo ======================================== -echo. - -REM Detect make command -set MAKE_CMD= -for %%m in (mingw32-make make gmake) do ( - where %%m >nul 2>&1 - if !errorlevel! equ 0 ( - set MAKE_CMD=%%m - goto :make_found - ) -) -:make_found - -if "!MAKE_CMD!"=="" ( - echo [ERROR] No make command found (tried mingw32-make, make, gmake^) - echo. - echo Please ensure MinGW is installed and in PATH - echo Download: https://www.mingw-w64.org/ - pause - exit /b 1 -) - -echo [INFO] Using make: !MAKE_CMD! -echo. - -REM Check dependencies -call :check_boost || exit /b 1 -call :check_pugixml || exit /b 1 - -REM Patch if needed -call :patch_senderenvelope - -REM Build library -echo [BUILD] Compiling Hermes library... -cd src\Hermes -!MAKE_CMD! -f Makefile.mingw clean -!MAKE_CMD! -f Makefile.mingw -if !errorlevel! neq 0 ( - echo [FAILED] Library build failed - cd ..\.. - pause - exit /b 1 -) -copy /Y Hermes.dll ..\..\Hermes.dll >nul -cd ..\.. - -REM Build and run tests -call :run_tests - -echo. -echo ======================================== -echo Build Complete! -echo ======================================== -pause -exit /b 0 - -REM ============================================================================ -REM Functions -REM ============================================================================ - -:check_boost -if exist "References\boost\version.hpp" ( - echo [OK] Boost found - exit /b 0 -) -echo [ERROR] Boost not found in References\boost\ -echo Download: https://archives.boost.io/release/1.78.0/source/boost_1_78_0.zip -pause -exit /b 1 - -:check_pugixml -if exist "References\pugixml\pugixml.hpp" ( - echo [OK] Pugixml found - exit /b 0 -) -echo [ERROR] Pugixml not found in References\pugixml\ -echo Download: https://github.com/zeux/pugixml -pause -exit /b 1 - -:patch_senderenvelope -cd src\Hermes -findstr /C:"#ifdef _WIN32" SenderEnvelope.cpp >nul 2>&1 -if !errorlevel! neq 0 ( - echo [PATCH] Fixing SenderEnvelope.cpp... - powershell -Command "$c = Get-Content SenderEnvelope.cpp -Raw; $c = $c -replace 'localtime_r\(&cnow, &local_tm\);', ('#ifdef _WIN32' + [char]10 + ' localtime_s(&local_tm, &cnow);' + [char]10 + '#else' + [char]10 + ' localtime_r(&cnow, &local_tm);' + [char]10 + '#endif'); Set-Content SenderEnvelope.cpp -Value $c -NoNewline" -) -cd ..\.. -exit /b 0 - -:run_tests -if exist "simple_test.cpp" ( - echo [TEST] Building simple_test... - g++ -std=c++17 -I src/include -L . -o simple_test.exe simple_test.cpp -lHermes -lws2_32 -lmswsock -static-libgcc -static-libstdc++ - if !errorlevel! equ 0 ( - simple_test.exe - ) -) - -if exist "References\boost_libs\libboost_unit_test_framework*.a" ( - echo [TEST] Building official test suite... - cd test\BoostTestHermes - !MAKE_CMD! -f Makefile.mingw clean - !MAKE_CMD! -f Makefile.mingw - if !errorlevel! equ 0 ( - copy /Y BoostTestHermes.exe ..\..\BoostTestHermes.exe >nul - cd ..\.. - BoostTestHermes.exe --run_test=* --detect_memory_leaks=0 - ) else ( - cd ..\.. - ) -) -exit /b 0 \ No newline at end of file diff --git a/setup_windows_without_cmake.bat b/setup_windows_without_cmake.bat deleted file mode 100644 index b0d6759..0000000 --- a/setup_windows_without_cmake.bat +++ /dev/null @@ -1,66 +0,0 @@ -@echo off -setlocal enabledelayedexpansion - -echo ======================================== -echo Hermes Build (Direct Compilation) -echo ======================================== -echo. - -call :check_boost || exit /b 1 -call :check_pugixml || exit /b 1 -call :patch_senderenvelope - -echo [BUILD] Compiling Hermes library... -cd src\Hermes - -del /Q *.o Hermes.dll 2>nul - -set CXXFLAGS=-std=c++17 -O2 -Wall -Wno-unknown-pragmas -DHERMES_EXPORTS -set INCLUDES=-I. -I../include -I../../References -I../../References/pugixml - -for %%f in (*.cpp) do ( - if not "%%f"=="stdafx.cpp" ( - echo %%f - g++ !CXXFLAGS! !INCLUDES! -c %%f - if !errorlevel! neq 0 exit /b 1 - ) -) - -echo pugixml.cpp -g++ -std=c++17 -O2 -I../../References/pugixml -c ../../References/pugixml/pugixml.cpp -if !errorlevel! neq 0 exit /b 1 - -g++ -shared -o Hermes.dll *.o -lws2_32 -lmswsock -liphlpapi -static-libgcc -static-libstdc++ -Wl,--export-all-symbols -if !errorlevel! neq 0 exit /b 1 - -copy /Y Hermes.dll ..\..\Hermes.dll >nul -cd ..\.. - -call :run_tests -echo [OK] Build complete! -pause -exit /b 0 - -:check_boost -if exist "References\boost\version.hpp" (echo [OK] Boost & exit /b 0) -echo [ERROR] Boost not found -exit /b 1 - -:check_pugixml -if exist "References\pugixml\pugixml.hpp" (echo [OK] Pugixml & exit /b 0) -echo [ERROR] Pugixml not found -exit /b 1 - -:patch_senderenvelope -cd src\Hermes -findstr /C:"#ifdef _WIN32" SenderEnvelope.cpp >nul 2>&1 || ( - powershell -Command "$c=gc SenderEnvelope.cpp -Raw; $c=$c -replace 'localtime_r\(&cnow, &local_tm\);','#ifdef _WIN32`n localtime_s(&local_tm, &cnow);`n#else`n localtime_r(&cnow, &local_tm);`n#endif'; sc SenderEnvelope.cpp $c -NoNewline" -) -cd ..\.. -exit /b 0 - -:run_tests -if exist simple_test.cpp ( - g++ -std=c++17 -I src/include -L . -o simple_test.exe simple_test.cpp -lHermes -lws2_32 -lmswsock -static-libgcc -static-libstdc++ && simple_test.exe -) -exit /b 0 \ No newline at end of file diff --git a/simple_test.cpp b/simple_test.cpp deleted file mode 100644 index bbcfd24..0000000 --- a/simple_test.cpp +++ /dev/null @@ -1,200 +0,0 @@ -// Simple Hermes Library Test -// No external dependencies except Hermes itself -#include -#include -#include -#include -#include "Hermes.h" - -#ifdef _WIN32 -#include -#else -#include -#endif - -// Simple event for synchronization -class SimpleEvent { -#ifdef _WIN32 - HANDLE handle; -public: - SimpleEvent() { handle = CreateEvent(NULL, TRUE, FALSE, NULL); } - ~SimpleEvent() { CloseHandle(handle); } - void Set() { SetEvent(handle); } - void Wait() { WaitForSingleObject(handle, INFINITE); } -#else - pthread_mutex_t mutex; - pthread_cond_t cond; - bool signaled; -public: - SimpleEvent() : signaled(false) { - pthread_mutex_init(&mutex, NULL); - pthread_cond_init(&cond, NULL); - } - ~SimpleEvent() { - pthread_mutex_destroy(&mutex); - pthread_cond_destroy(&cond); - } - void Set() { - pthread_mutex_lock(&mutex); - signaled = true; - pthread_cond_signal(&cond); - pthread_mutex_unlock(&mutex); - } - void Wait() { - pthread_mutex_lock(&mutex); - while (!signaled) pthread_cond_wait(&cond, &mutex); - pthread_mutex_unlock(&mutex); - } -#endif -}; - -// Global events for test synchronization -SimpleEvent downstreamConnected; -SimpleEvent upstreamConnected; - -// Callbacks -void OnDownstreamConnected(void*, uint32_t sessionId, EHermesState, const HermesConnectionInfo*) { - std::cout << "[DOWNSTREAM] Connected - Session " << sessionId << std::endl; - downstreamConnected.Set(); -} - -void OnUpstreamConnected(void*, uint32_t sessionId, EHermesState, const HermesConnectionInfo*) { - std::cout << "[UPSTREAM] Connected - Session " << sessionId << std::endl; - upstreamConnected.Set(); -} - -void OnTrace(void*, unsigned sessionId, EHermesTraceType type, HermesStringView trace) { - const char* typeStr = "UNKNOWN"; - switch(type) { - case eHERMES_TRACE_TYPE_DEBUG: typeStr = "DEBUG"; break; - case eHERMES_TRACE_TYPE_INFO: typeStr = "INFO"; break; - case eHERMES_TRACE_TYPE_WARNING: typeStr = "WARN"; break; - case eHERMES_TRACE_TYPE_ERROR: typeStr = "ERROR"; break; - case eHERMES_TRACE_TYPE_SENT: typeStr = "SENT"; break; - case eHERMES_TRACE_TYPE_RECEIVED: typeStr = "RECV"; break; - } - std::cout << "[" << typeStr << "] "; - std::cout.write(trace.m_pData, trace.m_size); - std::cout << std::endl; -} - -#ifdef _WIN32 -DWORD WINAPI RunDownstreamThread(void* param) { - RunHermesDownstream(static_cast(param)); - return 0; -} - -DWORD WINAPI RunUpstreamThread(void* param) { - RunHermesUpstream(static_cast(param)); - return 0; -} -#else -void* RunDownstreamThread(void* param) { - RunHermesDownstream(static_cast(param)); - return nullptr; -} - -void* RunUpstreamThread(void* param) { - RunHermesUpstream(static_cast(param)); - return nullptr; -} -#endif - -int main() { - std::cout << "========================================" << std::endl; - std::cout << " Hermes Library Self-Test" << std::endl; - std::cout << "========================================" << std::endl; - std::cout << std::endl; - - // Create downstream (server) - std::cout << "[1/5] Creating downstream connection..." << std::endl; - HermesDownstreamCallbacks downCallbacks = {}; - downCallbacks.m_connectedCallback = {OnDownstreamConnected, nullptr}; - downCallbacks.m_traceCallback = {OnTrace, nullptr}; - - HermesDownstream* pDown = CreateHermesDownstream(1, &downCallbacks); - - const char* machineId = "TestMachine"; - HermesDownstreamSettings downSettings = {}; - downSettings.m_machineId = {machineId, static_cast(strlen(machineId))}; - downSettings.m_port = 50100; - downSettings.m_checkAlivePeriodInSeconds = 60; - downSettings.m_reconnectWaitTimeInSeconds = 5; - downSettings.m_checkAliveResponseMode = eHERMES_CHECK_ALIVE_RESPONSE_MODE_AUTO; - downSettings.m_checkState = eHERMES_CHECK_STATE_SEND_AND_RECEIVE; - - EnableHermesDownstream(pDown, &downSettings); - -#ifdef _WIN32 - HANDLE downThread = CreateThread(0, 0, RunDownstreamThread, pDown, 0, 0); -#else - pthread_t downThread; - pthread_create(&downThread, NULL, RunDownstreamThread, pDown); -#endif - - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - - // Create upstream (client) - std::cout << "[2/5] Creating upstream connection..." << std::endl; - HermesUpstreamCallbacks upCallbacks = {}; - upCallbacks.m_connectedCallback = {OnUpstreamConnected, nullptr}; - upCallbacks.m_traceCallback = {OnTrace, nullptr}; - - HermesUpstream* pUp = CreateHermesUpstream(1, &upCallbacks); - - const char* localhost = "127.0.0.1"; - HermesUpstreamSettings upSettings = {}; - upSettings.m_machineId = {machineId, static_cast(strlen(machineId))}; - upSettings.m_hostAddress = {localhost, static_cast(strlen(localhost))}; - upSettings.m_port = 50100; - upSettings.m_checkAlivePeriodInSeconds = 60; - upSettings.m_reconnectWaitTimeInSeconds = 5; - upSettings.m_checkAliveResponseMode = eHERMES_CHECK_ALIVE_RESPONSE_MODE_AUTO; - upSettings.m_checkState = eHERMES_CHECK_STATE_SEND_AND_RECEIVE; - - EnableHermesUpstream(pUp, &upSettings); - -#ifdef _WIN32 - HANDLE upThread = CreateThread(0, 0, RunUpstreamThread, pUp, 0, 0); -#else - pthread_t upThread; - pthread_create(&upThread, NULL, RunUpstreamThread, pUp); -#endif - - std::cout << "[3/5] Waiting for connections..." << std::endl; - - // Wait for both to connect - downstreamConnected.Wait(); - upstreamConnected.Wait(); - - std::cout << "[4/5] Both connections established!" << std::endl; - std::cout << "[5/5] Cleaning up..." << std::endl; - - std::this_thread::sleep_for(std::chrono::seconds(1)); - - // Cleanup - StopHermesUpstream(pUp); - StopHermesDownstream(pDown); - -#ifdef _WIN32 - WaitForSingleObject(upThread, INFINITE); - WaitForSingleObject(downThread, INFINITE); - CloseHandle(upThread); - CloseHandle(downThread); -#else - pthread_join(upThread, NULL); - pthread_join(downThread, NULL); -#endif - - DeleteHermesUpstream(pUp); - DeleteHermesDownstream(pDown); - - std::cout << std::endl; - std::cout << "========================================" << std::endl; - std::cout << " TEST PASSED!" << std::endl; - std::cout << "========================================" << std::endl; - std::cout << "The Hermes library is working correctly." << std::endl; - std::cout << std::endl; - - return 0; -} \ No newline at end of file diff --git a/src/Hermes.sln b/src/Hermes.sln deleted file mode 100644 index a84f522..0000000 --- a/src/Hermes.sln +++ /dev/null @@ -1,47 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28010.2050 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Hermes", "Hermes\Hermes.vcxproj", "{A52420C5-D236-47B7-883E-2166E83C2F43}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BoostTestHermes", "..\test\BoostTestHermes\BoostTestHermes.vcxproj", "{B4CB35B8-B453-4B1E-8469-5A8059F2CE25}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A52420C5-D236-47B7-883E-2166E83C2F43}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {A52420C5-D236-47B7-883E-2166E83C2F43}.Debug|x64.ActiveCfg = Debug|x64 - {A52420C5-D236-47B7-883E-2166E83C2F43}.Debug|x64.Build.0 = Debug|x64 - {A52420C5-D236-47B7-883E-2166E83C2F43}.Debug|x86.ActiveCfg = Debug|Win32 - {A52420C5-D236-47B7-883E-2166E83C2F43}.Debug|x86.Build.0 = Debug|Win32 - {A52420C5-D236-47B7-883E-2166E83C2F43}.Release|Any CPU.ActiveCfg = Release|Win32 - {A52420C5-D236-47B7-883E-2166E83C2F43}.Release|x64.ActiveCfg = Release|x64 - {A52420C5-D236-47B7-883E-2166E83C2F43}.Release|x64.Build.0 = Release|x64 - {A52420C5-D236-47B7-883E-2166E83C2F43}.Release|x86.ActiveCfg = Release|Win32 - {A52420C5-D236-47B7-883E-2166E83C2F43}.Release|x86.Build.0 = Release|Win32 - {B4CB35B8-B453-4B1E-8469-5A8059F2CE25}.Debug|Any CPU.ActiveCfg = Debug|Win32 - {B4CB35B8-B453-4B1E-8469-5A8059F2CE25}.Debug|x64.ActiveCfg = Debug|x64 - {B4CB35B8-B453-4B1E-8469-5A8059F2CE25}.Debug|x64.Build.0 = Debug|x64 - {B4CB35B8-B453-4B1E-8469-5A8059F2CE25}.Debug|x86.ActiveCfg = Debug|Win32 - {B4CB35B8-B453-4B1E-8469-5A8059F2CE25}.Debug|x86.Build.0 = Debug|Win32 - {B4CB35B8-B453-4B1E-8469-5A8059F2CE25}.Release|Any CPU.ActiveCfg = Release|Win32 - {B4CB35B8-B453-4B1E-8469-5A8059F2CE25}.Release|x64.ActiveCfg = Release|x64 - {B4CB35B8-B453-4B1E-8469-5A8059F2CE25}.Release|x64.Build.0 = Release|x64 - {B4CB35B8-B453-4B1E-8469-5A8059F2CE25}.Release|x86.ActiveCfg = Release|Win32 - {B4CB35B8-B453-4B1E-8469-5A8059F2CE25}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {1C997128-625F-459D-A033-A1C34AC68751} - EndGlobalSection -EndGlobal diff --git a/src/Hermes/CMakeLists.txt b/src/Hermes/CMakeLists.txt new file mode 100644 index 0000000..43bfcf0 --- /dev/null +++ b/src/Hermes/CMakeLists.txt @@ -0,0 +1,43 @@ +# ============================================================================== +# src/Hermes/CMakeLists.txt (Universal Cross-Platform) +# ============================================================================== +file(GLOB HERMES_SOURCES "*.cpp") + +# Pugixml is compiled directly into the library +list(APPEND HERMES_SOURCES "../../References/pugixml/pugixml.cpp") + +# Windows explicitly requires the .def file for DLL exports; Linux/Mac ignore it +if(WIN32) + add_library(hermes SHARED ${HERMES_SOURCES} Hermes.def) +else() + add_library(hermes SHARED ${HERMES_SOURCES}) +endif() + +# Public Includes (What users of the library need to see) +target_include_directories(hermes PUBLIC $) + +# Private Includes (Internal dependencies) +target_include_directories(hermes PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../../References + ${CMAKE_CURRENT_SOURCE_DIR}/../../References/pugixml +) + +# Link the Boost Headers and Cross-Platform Threads +target_link_libraries(hermes PRIVATE Boost::boost Threads::Threads) + +# Core Definitions +target_compile_definitions(hermes PRIVATE + HERMES_EXPORTS + BOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT +) + +# OS-Specific Linker Flags +if(WIN32) + # All Windows compilers need the socket libraries + target_link_libraries(hermes PRIVATE ws2_32 mswsock iphlpapi) + + # ONLY MinGW (GCC on Windows) needs the static standard library flags + if(MINGW) + target_link_options(hermes PRIVATE "-static-libgcc" "-static-libstdc++") + endif() +endif() \ No newline at end of file diff --git a/src/Hermes/Hermes.vcxproj b/src/Hermes/Hermes.vcxproj deleted file mode 100644 index 495ad7e..0000000 --- a/src/Hermes/Hermes.vcxproj +++ /dev/null @@ -1,255 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - {A52420C5-D236-47B7-883E-2166E83C2F43} - SAK - SAK - SAK - SAK - Win32Proj - Hermes - 10.0 - - - - DynamicLibrary - true - v143 - NotSet - - - DynamicLibrary - false - v143 - true - NotSet - - - DynamicLibrary - true - v143 - NotSet - - - DynamicLibrary - false - v143 - true - NotSet - - - - - - - - - - - - - - - - - - - - - true - ..\..\bin\$(PlatformTarget)\$(Configuration)\ - ..\..\tmp\$(ProjectName)_$(Configuration)_$(PlatformTarget)\ - - - true - ..\..\bin\$(PlatformTarget)\$(Configuration)\ - ..\..\tmp\$(ProjectName)_$(Configuration)_$(PlatformTarget)\ - - - false - ..\..\bin\$(PlatformTarget)\$(Configuration)\ - ..\..\tmp\$(ProjectName)_$(Configuration)_$(PlatformTarget)\ - - - false - ..\..\bin\$(PlatformTarget)\$(Configuration)\ - ..\..\tmp\$(ProjectName)_$(Configuration)_$(PlatformTarget)\ - - - - Use - Level4 - Disabled - HERMES_LIB;_DEBUG;_WINDOWS;_USRDLL;HERMES_EXPORTS;%(PreprocessorDefinitions) - $(ProjectDir)\..\..\References; $(ProjectDir)\..\include - MultiThreadedDebug - /Zc:threadSafeInit- - stdcpp20 - - - Windows - true - $(ProjectDir)\..\..\lib\$(Configuration)\$(PlatformTarget)\$(TargetName).lib - ..\..\References\lib32 - - - - - - Use - Level4 - Disabled - HERMES_LIB;_DEBUG;_WINDOWS;_USRDLL;HERMES_EXPORTS;%(PreprocessorDefinitions) - $(ProjectDir)\..\..\References; $(ProjectDir)\..\include - MultiThreadedDebug - /Zc:threadSafeInit- - stdcpp20 - - - Windows - true - $(ProjectDir)\..\..\lib\$(Configuration)\$(PlatformTarget)\$(TargetName).lib - ..\..\References\lib64 - - - - - Level4 - Use - MaxSpeed - HERMES_LIB;NDEBUG;_WINDOWS;_USRDLL;HERMES_EXPORTS;%(PreprocessorDefinitions) - $(ProjectDir)\..\..\References; $(ProjectDir)\..\include - MultiThreaded - /Zc:threadSafeInit- - stdcpp20 - - - Windows - true - true - true - $(ProjectDir)\..\..\lib\$(Configuration)\$(PlatformTarget)\$(TargetName).lib - ..\..\References\lib32 - - - - - - Level4 - Use - MaxSpeed - true - HERMES_LIB;NDEBUG;_WINDOWS;_USRDLL;HERMES_EXPORTS;%(PreprocessorDefinitions) - $(ProjectDir)\..\..\References; $(ProjectDir)\..\include - MultiThreaded - /Zc:threadSafeInit- - stdcpp20 - - - Windows - true - true - true - $(ProjectDir)\..\..\lib\$(Configuration)\$(PlatformTarget)\$(TargetName).lib - ..\..\References\lib64 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - - - Create - Create - Create - Create - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Hermes/Hermes.vcxproj.filters b/src/Hermes/Hermes.vcxproj.filters deleted file mode 100644 index 7df317a..0000000 --- a/src/Hermes/Hermes.vcxproj.filters +++ /dev/null @@ -1,235 +0,0 @@ - - - - - {1206491f-c7dc-4ff7-9428-24ec0363de3a} - - - {27ccef39-5c10-4813-b489-372b6957ed1b} - - - {1e1ec4f4-39f0-4631-a633-9113d04d378e} - - - {33074580-b1ec-4da0-8a9f-348707f45aaa} - - - {98e706fa-0d5f-45c9-9941-d290afc0453f} - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {a1b6f957-3633-4457-8869-555143b5cdba} - - - {217b355f-4db9-4366-8f3e-2fc173b41223} - - - {4035d6a8-f053-4d09-92f9-856f89ef72d0} - - - {b1011605-5304-4521-bc19-57db95f24442} - - - {3a3fc53b-b1ec-4d42-9082-17db5afd9423} - - - {2c66b745-fc16-4ca7-937b-d360d6105e07} - - - - - PrecompiledHeaders - - - Service - - - NetworkCommunication - - - Infra - - - NetworkCommunication - - - Downstream - - - Downstream - - - Downstream - - - Upstream - - - Upstream - - - Upstream - - - Configuration - - - Configuration - - - Service - - - Infra - - - Public - - - Public - - - Public - - - Public - - - Public - - - Public - - - Public - - - Public - - - Public - - - Public - - - Infra - - - Serialization - - - Serialization - - - Serialization - - - Serialization - - - Serialization - - - VerticalService - - - VerticalService - - - VerticalClient - - - VerticalClient - - - Service - - - - - PrecompiledHeaders - - - NetworkCommunication - - - NetworkCommunication - - - Downstream - - - Downstream - - - Downstream - - - Downstream - - - Upstream - - - Upstream - - - Upstream - - - Upstream - - - Configuration - - - Configuration - - - Configuration - - - Configuration - - - Serialization - - - Serialization - - - Serialization - - - Serialization - - - Serialization - - - VerticalService - - - VerticalService - - - VerticalService - - - VerticalClient - - - VerticalClient - - - VerticalClient - - - Pugixml - - - \ No newline at end of file diff --git a/src/Hermes/Makefile b/src/Hermes/Makefile deleted file mode 100644 index 945f602..0000000 --- a/src/Hermes/Makefile +++ /dev/null @@ -1,43 +0,0 @@ -DBGFLAGS = -O2 -g0 -DNDEBUG -ifeq ($(DEBUG),1) -DBGFLAGS = -O0 -g3 -D_DEBUG -endif - -VERSION= 3:1 -CFLAGS = -std=c++17 -LFLAGS = -lpugixml - -CCOMPILER=libtool --mode=compile g++ -Wall -Wno-unused -fPIC -shared $(DBGFLAGS) -D_REENTRANT -DENV_LINUX -I. -I../include $(CFLAGS) -# -fvisibility=hidden - -SYSLIBRARIES= - -LIBS = - -EXECUTABLE=libhermes.la - -LINK=libtool --mode=link g++ -shared -rpath /usr/lib64 -version-info $(VERSION) -#-fvisibility=hidden-export-symbols-regex 'Hermes' - -OBJECTS = AsioClient.lo AsioServer.lo ConfigurationClient.lo ConfigurationService.lo ConfigurationServiceSerializer.lo \ - ConfigurationServiceSession.lo DeserializationHelper.lo Downstream.lo DownstreamSerializer.lo DownstreamSession.lo DownstreamStateMachine.lo \ - MessageDispatcher.lo MessageSerialization.lo SenderEnvelope.lo Serialization.lo Upstream.lo \ - UpstreamSerializer.lo UpstreamSession.lo UpstreamStateMachine.lo \ - VerticalClient.lo VerticalClientSerializer.lo VerticalClientSession.lo VerticalService.lo \ - VerticalServiceSerializer.lo VerticalServiceSession.lo - - - -default: $(EXECUTABLE) - -$(EXECUTABLE): $(OBJECTS) - $(LINK) $(SYSLDFLAGS) $(LDFLAGS) -o $(EXECUTABLE) $(OBJECTS) -Wl,-whole-archive $(LIBS) -Wl,-no-whole-archive - -%.lo: %.cpp - $(CCOMPILER) -c $< -o $@ - -clean: - rm -f $(OBJECTS) $(EXECUTABLE) - - - diff --git a/src/Hermes/Makefile.mingw b/src/Hermes/Makefile.mingw deleted file mode 100644 index c341734..0000000 --- a/src/Hermes/Makefile.mingw +++ /dev/null @@ -1,25 +0,0 @@ -VERSION = 3:1 -DBGFLAGS = -O2 -g0 -DNDEBUG -CXXFLAGS = -std=c++17 -Wall -Wno-unknown-pragmas -fPIC -shared $(DBGFLAGS) -DHERMES_EXPORTS -INCLUDES = -I. -I../include -I../../References -I../../References/pugixml -LIBS = -lws2_32 -lmswsock -liphlpapi -static-libgcc -static-libstdc++ - -SOURCES = $(wildcard *.cpp) -OBJECTS = $(SOURCES:.cpp=.o) ../../References/pugixml/pugixml.o -EXECUTABLE = Hermes.dll - -all: $(EXECUTABLE) - -$(EXECUTABLE): $(OBJECTS) - g++ -shared -o $@ $(OBJECTS) $(LIBS) -Wl,--export-all-symbols - -%.o: %.cpp - g++ $(CXXFLAGS) $(INCLUDES) -c $< -o $@ - -../../References/pugixml/pugixml.o: ../../References/pugixml/pugixml.cpp - g++ -std=c++17 -O2 -Wall -I../../References/pugixml -c $< -o $@ - -clean: - del /Q *.o $(EXECUTABLE) 2>nul - -.PHONY: all clean \ No newline at end of file diff --git a/src/Hermes/SenderEnvelope.cpp b/src/Hermes/SenderEnvelope.cpp index b51acb0..46b0b8d 100644 --- a/src/Hermes/SenderEnvelope.cpp +++ b/src/Hermes/SenderEnvelope.cpp @@ -18,10 +18,12 @@ namespace Hermes auto fraction = now - seconds; time_t cnow = std::chrono::system_clock::to_time_t(now); tm local_tm; -#ifdef _WINDOWS - localtime_s(&local_tm, &cnow); +#if defined(_WIN32) + // Windows uses localtime_s (arguments are destination, then source) + localtime_s(&local_tm, &cnow); #else - localtime_r(&cnow, &local_tm); + // Linux/POSIX uses localtime_r (arguments are source, then destination) + localtime_r(&cnow, &local_tm); #endif std::ostringstream oss; oss << std::put_time(&local_tm, "%Y-%m-%dT%H:%M:%S."); diff --git a/src/include/Hermes.hpp b/src/include/Hermes.hpp index 60078da..1ef7b98 100644 --- a/src/include/Hermes.hpp +++ b/src/include/Hermes.hpp @@ -916,7 +916,7 @@ namespace Hermes return{configuration, error}; } - inline Error Hermes::SetConfiguration(StringView hostName, const SetConfigurationData& configuration, + inline Error SetConfiguration(StringView hostName, const SetConfigurationData& configuration, unsigned timeoutInSeconds, Hermes::CurrentConfigurationData* out_pConfiguration, // resulting configuration std::vector* out_pNotifications, // out: notification data diff --git a/src/include/HermesModern.hpp b/src/include/HermesModern.hpp new file mode 100644 index 0000000..a2e42a9 --- /dev/null +++ b/src/include/HermesModern.hpp @@ -0,0 +1,522 @@ +// ============================================================================== +// src/include/HermesModern.hpp +// Modern C++ Wrapper for the Hermes Standard Library (Complete Suite) +// ============================================================================== +#pragma once + +#include "Hermes.hpp" +#include +#include +#include +#include + +namespace Hermes { +namespace Modern { + +// ============================================================================== +// Downstream Wrapper (Receiver - Horizontal) +// ============================================================================== +class Downstream { +public: + using ConnectedCallback = std::function; + using DisconnectedCallback = std::function; + using StateChangeCallback = std::function; + using TraceCallback = std::function; + + using ServiceDescriptionCallback = std::function; + using MachineReadyCallback = std::function; + using RevokeMachineReadyCallback = std::function; + using StartTransportCallback = std::function; + using StopTransportCallback = std::function; + using QueryBoardInfoCallback = std::function; + + using NotificationCallback = std::function; + using CheckAliveCallback = std::function; + using CommandCallback = std::function; + + Downstream(unsigned laneId) : m_laneId(laneId), m_isRunning(false) { + m_callbackWrapper = std::make_unique(this); + m_downstream = std::make_unique(laneId, *m_callbackWrapper); + } + + ~Downstream() { Stop(); } + + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } + void RegisterStateChangeCallback(StateChangeCallback cb) { m_onStateChange = cb; } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } + + void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb) { m_onServiceDescription = cb; } + void RegisterMachineReadyCallback(MachineReadyCallback cb) { m_onMachineReady = cb; } + void RegisterRevokeMachineReadyCallback(RevokeMachineReadyCallback cb) { m_onRevokeMachineReady = cb; } + void RegisterStartTransportCallback(StartTransportCallback cb) { m_onStartTransport = cb; } + void RegisterStopTransportCallback(StopTransportCallback cb) { m_onStopTransport = cb; } + void RegisterQueryBoardInfoCallback(QueryBoardInfoCallback cb) { m_onQueryBoardInfo = cb; } + void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } + void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } + void RegisterCommandCallback(CommandCallback cb) { m_onCommand = cb; } + + void Enable(const DownstreamSettings& settings) { + if (m_isRunning) return; + m_downstream->Enable(settings); + m_isRunning = true; + m_networkThread = std::thread([this]() { m_downstream->Run(); }); + } + + void Stop() { + if (m_isRunning) { + m_downstream->Stop(); + if (m_networkThread.joinable()) m_networkThread.join(); + m_isRunning = false; + } + } + + template + void Signal(unsigned sessionId, const T& data) { m_downstream->Signal(sessionId, data); } + +private: + class InternalCallbackWrapper : public Hermes::IDownstreamCallback { + Downstream* m_parent; + public: + InternalCallbackWrapper(Downstream* parent) : m_parent(parent) {} + + void OnConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { + if (m_parent->m_onConnected) m_parent->m_onConnected(info); + } + void OnDisconnected(unsigned sessionId, Hermes::EState state, const Hermes::Error& error) override { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error); + } + void OnState(unsigned sessionId, Hermes::EState state) override { + if (m_parent->m_onStateChange) m_parent->m_onStateChange(state); + } + void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { + if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); + } + + void On(unsigned sessionId, Hermes::EState state, const Hermes::ServiceDescriptionData& data) override { + if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(data); + } + void On(unsigned sessionId, Hermes::EState state, const Hermes::MachineReadyData& data) override { + if (m_parent->m_onMachineReady) m_parent->m_onMachineReady(data); + } + void On(unsigned sessionId, Hermes::EState state, const Hermes::RevokeMachineReadyData& data) override { + if (m_parent->m_onRevokeMachineReady) m_parent->m_onRevokeMachineReady(data); + } + void On(unsigned sessionId, Hermes::EState state, const Hermes::StartTransportData& data) override { + if (m_parent->m_onStartTransport) m_parent->m_onStartTransport(data); + } + void On(unsigned sessionId, Hermes::EState state, const Hermes::StopTransportData& data) override { + if (m_parent->m_onStopTransport) m_parent->m_onStopTransport(data); + } + void On(unsigned sessionId, const Hermes::QueryBoardInfoData& data) override { + if (m_parent->m_onQueryBoardInfo) m_parent->m_onQueryBoardInfo(data); + } + void On(unsigned sessionId, const Hermes::NotificationData& data) override { + if (m_parent->m_onNotification) m_parent->m_onNotification(data); + } + void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); + } + void On(unsigned sessionId, const Hermes::CommandData& data) override { + if (m_parent->m_onCommand) m_parent->m_onCommand(data); + } + }; + + unsigned m_laneId; + std::atomic m_isRunning; + std::thread m_networkThread; + std::unique_ptr m_callbackWrapper; + std::unique_ptr m_downstream; + + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + StateChangeCallback m_onStateChange; + TraceCallback m_onTrace; + + ServiceDescriptionCallback m_onServiceDescription; + MachineReadyCallback m_onMachineReady; + RevokeMachineReadyCallback m_onRevokeMachineReady; + StartTransportCallback m_onStartTransport; + StopTransportCallback m_onStopTransport; + QueryBoardInfoCallback m_onQueryBoardInfo; + + NotificationCallback m_onNotification; + CheckAliveCallback m_onCheckAlive; + CommandCallback m_onCommand; +}; + +// ============================================================================== +// Upstream Wrapper (Sender - Horizontal) +// ============================================================================== +class Upstream { +public: + using ConnectedCallback = std::function; + using DisconnectedCallback = std::function; + using StateChangeCallback = std::function; + using TraceCallback = std::function; + + using ServiceDescriptionCallback = std::function; + using BoardAvailableCallback = std::function; + using RevokeBoardAvailableCallback = std::function; + using TransportFinishedCallback = std::function; + using BoardForecastCallback = std::function; + using SendBoardInfoCallback = std::function; + + using NotificationCallback = std::function; + using CheckAliveCallback = std::function; + using CommandCallback = std::function; + + Upstream(unsigned laneId) : m_laneId(laneId), m_isRunning(false) { + m_callbackWrapper = std::make_unique(this); + m_upstream = std::make_unique(laneId, *m_callbackWrapper); + } + + ~Upstream() { Stop(); } + + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } + void RegisterStateChangeCallback(StateChangeCallback cb) { m_onStateChange = cb; } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } + + void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb) { m_onServiceDescription = cb; } + void RegisterBoardAvailableCallback(BoardAvailableCallback cb) { m_onBoardAvailable = cb; } + void RegisterRevokeBoardAvailableCallback(RevokeBoardAvailableCallback cb) { m_onRevokeBoardAvailable = cb; } + void RegisterTransportFinishedCallback(TransportFinishedCallback cb) { m_onTransportFinished = cb; } + void RegisterBoardForecastCallback(BoardForecastCallback cb) { m_onBoardForecast = cb; } + void RegisterSendBoardInfoCallback(SendBoardInfoCallback cb) { m_onSendBoardInfo = cb; } + void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } + void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } + void RegisterCommandCallback(CommandCallback cb) { m_onCommand = cb; } + + void Enable(const UpstreamSettings& settings) { + if (m_isRunning) return; + m_upstream->Enable(settings); + m_isRunning = true; + m_networkThread = std::thread([this]() { m_upstream->Run(); }); + } + + void Stop() { + if (m_isRunning) { + m_upstream->Stop(); + if (m_networkThread.joinable()) m_networkThread.join(); + m_isRunning = false; + } + } + + template + void Signal(unsigned sessionId, const T& data) { m_upstream->Signal(sessionId, data); } + +private: + class InternalCallbackWrapper : public Hermes::IUpstreamCallback { + Upstream* m_parent; + public: + InternalCallbackWrapper(Upstream* parent) : m_parent(parent) {} + + void OnConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { + if (m_parent->m_onConnected) m_parent->m_onConnected(info); + } + void OnDisconnected(unsigned sessionId, Hermes::EState state, const Hermes::Error& error) override { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error); + } + void OnState(unsigned sessionId, Hermes::EState state) override { + if (m_parent->m_onStateChange) m_parent->m_onStateChange(state); + } + void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { + if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); + } + + void On(unsigned sessionId, Hermes::EState state, const Hermes::ServiceDescriptionData& data) override { + if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(data); + } + void On(unsigned sessionId, Hermes::EState state, const Hermes::BoardAvailableData& data) override { + if (m_parent->m_onBoardAvailable) m_parent->m_onBoardAvailable(data); + } + void On(unsigned sessionId, Hermes::EState state, const Hermes::RevokeBoardAvailableData& data) override { + if (m_parent->m_onRevokeBoardAvailable) m_parent->m_onRevokeBoardAvailable(data); + } + void On(unsigned sessionId, Hermes::EState state, const Hermes::TransportFinishedData& data) override { + if (m_parent->m_onTransportFinished) m_parent->m_onTransportFinished(data); + } + void On(unsigned sessionId, Hermes::EState state, const Hermes::BoardForecastData& data) override { + if (m_parent->m_onBoardForecast) m_parent->m_onBoardForecast(data); + } + void On(unsigned sessionId, const Hermes::SendBoardInfoData& data) override { + if (m_parent->m_onSendBoardInfo) m_parent->m_onSendBoardInfo(data); + } + void On(unsigned sessionId, const Hermes::NotificationData& data) override { + if (m_parent->m_onNotification) m_parent->m_onNotification(data); + } + void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); + } + void On(unsigned sessionId, const Hermes::CommandData& data) override { + if (m_parent->m_onCommand) m_parent->m_onCommand(data); + } + }; + + unsigned m_laneId; + std::atomic m_isRunning; + std::thread m_networkThread; + std::unique_ptr m_callbackWrapper; + std::unique_ptr m_upstream; + + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + StateChangeCallback m_onStateChange; + TraceCallback m_onTrace; + + ServiceDescriptionCallback m_onServiceDescription; + BoardAvailableCallback m_onBoardAvailable; + RevokeBoardAvailableCallback m_onRevokeBoardAvailable; + TransportFinishedCallback m_onTransportFinished; + BoardForecastCallback m_onBoardForecast; + SendBoardInfoCallback m_onSendBoardInfo; + + NotificationCallback m_onNotification; + CheckAliveCallback m_onCheckAlive; + CommandCallback m_onCommand; +}; + + +// ============================================================================== +// VerticalService Wrapper (Machine Server - Vertical) +// ============================================================================== +class VerticalService { +public: + using ConnectedCallback = std::function; + using DisconnectedCallback = std::function; + using TraceCallback = std::function; + + using SupervisoryServiceDescriptionCallback = std::function; + using GetConfigurationCallback = std::function; + using SetConfigurationCallback = std::function; + using SendWorkOrderInfoCallback = std::function; + using QueryHermesCapabilitiesCallback = std::function; + + using NotificationCallback = std::function; + using CheckAliveCallback = std::function; + + VerticalService() : m_isRunning(false) { + m_callbackWrapper = std::make_unique(this); + m_verticalService = std::make_unique(*m_callbackWrapper); + } + + ~VerticalService() { Stop(); } + + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } + + void RegisterSupervisoryServiceDescriptionCallback(SupervisoryServiceDescriptionCallback cb) { m_onSupervisoryServiceDescription = cb; } + void RegisterGetConfigurationCallback(GetConfigurationCallback cb) { m_onGetConfiguration = cb; } + void RegisterSetConfigurationCallback(SetConfigurationCallback cb) { m_onSetConfiguration = cb; } + void RegisterSendWorkOrderInfoCallback(SendWorkOrderInfoCallback cb) { m_onSendWorkOrderInfo = cb; } + void RegisterQueryHermesCapabilitiesCallback(QueryHermesCapabilitiesCallback cb) { m_onQueryHermesCapabilities = cb; } + void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } + void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } + + void Enable(const VerticalServiceSettings& settings) { + if (m_isRunning) return; + m_verticalService->Enable(settings); + m_isRunning = true; + m_networkThread = std::thread([this]() { m_verticalService->Run(); }); + } + + void Stop() { + if (m_isRunning) { + m_verticalService->Stop(); + if (m_networkThread.joinable()) m_networkThread.join(); + m_isRunning = false; + } + } + + template + void Signal(unsigned sessionId, const T& data) { m_verticalService->Signal(sessionId, data); } + + template + void SignalBroadcast(const T& data) { m_verticalService->Signal(data); } + +private: + class InternalCallbackWrapper : public Hermes::IVerticalServiceCallback { + VerticalService* m_parent; + public: + InternalCallbackWrapper(VerticalService* parent) : m_parent(parent) {} + + void OnConnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::ConnectionInfo& info) override { + if (m_parent->m_onConnected) m_parent->m_onConnected(info, state); + } + void OnDisconnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::Error& error) override { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error, state); + } + void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { + if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); + } + + void On(unsigned sessionId, Hermes::EVerticalState state, const Hermes::SupervisoryServiceDescriptionData& data) override { + if (m_parent->m_onSupervisoryServiceDescription) m_parent->m_onSupervisoryServiceDescription(data, state); + } + void On(unsigned sessionId, const Hermes::GetConfigurationData& data, const Hermes::ConnectionInfo& info) override { + if (m_parent->m_onGetConfiguration) m_parent->m_onGetConfiguration(data, info); + } + void On(unsigned sessionId, const Hermes::SetConfigurationData& data, const Hermes::ConnectionInfo& info) override { + if (m_parent->m_onSetConfiguration) m_parent->m_onSetConfiguration(data, info); + } + void On(unsigned sessionId, const Hermes::SendWorkOrderInfoData& data) override { + if (m_parent->m_onSendWorkOrderInfo) m_parent->m_onSendWorkOrderInfo(data); + } + void On(unsigned sessionId, const Hermes::QueryHermesCapabilitiesData& data) override { + if (m_parent->m_onQueryHermesCapabilities) m_parent->m_onQueryHermesCapabilities(data); + } + void On(unsigned sessionId, const Hermes::NotificationData& data) override { + if (m_parent->m_onNotification) m_parent->m_onNotification(data); + } + void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); + } + }; + + std::atomic m_isRunning; + std::thread m_networkThread; + std::unique_ptr m_callbackWrapper; + std::unique_ptr m_verticalService; + + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + TraceCallback m_onTrace; + + SupervisoryServiceDescriptionCallback m_onSupervisoryServiceDescription; + GetConfigurationCallback m_onGetConfiguration; + SetConfigurationCallback m_onSetConfiguration; + SendWorkOrderInfoCallback m_onSendWorkOrderInfo; + QueryHermesCapabilitiesCallback m_onQueryHermesCapabilities; + NotificationCallback m_onNotification; + CheckAliveCallback m_onCheckAlive; +}; + + +// ============================================================================== +// VerticalClient Wrapper (MES/Factory Cloud Client - Vertical) +// ============================================================================== +class VerticalClient { +public: + using ConnectedCallback = std::function; + using DisconnectedCallback = std::function; + using TraceCallback = std::function; + + using SupervisoryServiceDescriptionCallback = std::function; + using BoardArrivedCallback = std::function; + using BoardDepartedCallback = std::function; + using QueryWorkOrderInfoCallback = std::function; + using ReplyWorkOrderInfoCallback = std::function; + using CurrentConfigurationCallback = std::function; + using SendHermesCapabilitiesCallback = std::function; + + using NotificationCallback = std::function; + using CheckAliveCallback = std::function; + + VerticalClient() : m_isRunning(false) { + m_callbackWrapper = std::make_unique(this); + m_verticalClient = std::make_unique(*m_callbackWrapper); + } + + ~VerticalClient() { Stop(); } + + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } + + void RegisterSupervisoryServiceDescriptionCallback(SupervisoryServiceDescriptionCallback cb) { m_onSupervisoryServiceDescription = cb; } + void RegisterBoardArrivedCallback(BoardArrivedCallback cb) { m_onBoardArrived = cb; } + void RegisterBoardDepartedCallback(BoardDepartedCallback cb) { m_onBoardDeparted = cb; } + void RegisterQueryWorkOrderInfoCallback(QueryWorkOrderInfoCallback cb) { m_onQueryWorkOrderInfo = cb; } + void RegisterReplyWorkOrderInfoCallback(ReplyWorkOrderInfoCallback cb) { m_onReplyWorkOrderInfo = cb; } + void RegisterCurrentConfigurationCallback(CurrentConfigurationCallback cb) { m_onCurrentConfiguration = cb; } + void RegisterSendHermesCapabilitiesCallback(SendHermesCapabilitiesCallback cb) { m_onSendHermesCapabilities = cb; } + void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } + void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } + + void Enable(const VerticalClientSettings& settings) { + if (m_isRunning) return; + m_verticalClient->Enable(settings); + m_isRunning = true; + m_networkThread = std::thread([this]() { m_verticalClient->Run(); }); + } + + void Stop() { + if (m_isRunning) { + m_verticalClient->Stop(); + if (m_networkThread.joinable()) m_networkThread.join(); + m_isRunning = false; + } + } + + template + void Signal(unsigned sessionId, const T& data) { m_verticalClient->Signal(sessionId, data); } + +private: + class InternalCallbackWrapper : public Hermes::IVerticalClientCallback { + VerticalClient* m_parent; + public: + InternalCallbackWrapper(VerticalClient* parent) : m_parent(parent) {} + + void OnConnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::ConnectionInfo& info) override { + if (m_parent->m_onConnected) m_parent->m_onConnected(info, state); + } + void OnDisconnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::Error& error) override { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error, state); + } + void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { + if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); + } + + void On(unsigned sessionId, Hermes::EVerticalState state, const Hermes::SupervisoryServiceDescriptionData& data) override { + if (m_parent->m_onSupervisoryServiceDescription) m_parent->m_onSupervisoryServiceDescription(data, state); + } + void On(unsigned sessionId, const Hermes::BoardArrivedData& data) override { + if (m_parent->m_onBoardArrived) m_parent->m_onBoardArrived(data); + } + void On(unsigned sessionId, const Hermes::BoardDepartedData& data) override { + if (m_parent->m_onBoardDeparted) m_parent->m_onBoardDeparted(data); + } + void On(unsigned sessionId, const Hermes::QueryWorkOrderInfoData& data) override { + if (m_parent->m_onQueryWorkOrderInfo) m_parent->m_onQueryWorkOrderInfo(data); + } + void On(unsigned sessionId, const Hermes::ReplyWorkOrderInfoData& data) override { + if (m_parent->m_onReplyWorkOrderInfo) m_parent->m_onReplyWorkOrderInfo(data); + } + void On(unsigned sessionId, const Hermes::CurrentConfigurationData& data) override { + if (m_parent->m_onCurrentConfiguration) m_parent->m_onCurrentConfiguration(data); + } + void On(unsigned sessionId, const Hermes::SendHermesCapabilitiesData& data) override { + if (m_parent->m_onSendHermesCapabilities) m_parent->m_onSendHermesCapabilities(data); + } + void On(unsigned sessionId, const Hermes::NotificationData& data) override { + if (m_parent->m_onNotification) m_parent->m_onNotification(data); + } + void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); + } + }; + + std::atomic m_isRunning; + std::thread m_networkThread; + std::unique_ptr m_callbackWrapper; + std::unique_ptr m_verticalClient; + + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + TraceCallback m_onTrace; + + SupervisoryServiceDescriptionCallback m_onSupervisoryServiceDescription; + BoardArrivedCallback m_onBoardArrived; + BoardDepartedCallback m_onBoardDeparted; + QueryWorkOrderInfoCallback m_onQueryWorkOrderInfo; + ReplyWorkOrderInfoCallback m_onReplyWorkOrderInfo; + CurrentConfigurationCallback m_onCurrentConfiguration; + SendHermesCapabilitiesCallback m_onSendHermesCapabilities; + NotificationCallback m_onNotification; + CheckAliveCallback m_onCheckAlive; +}; + +} // namespace Modern +} // namespace Hermes \ No newline at end of file diff --git a/src/include/HermesStringView.h b/src/include/HermesStringView.h index b4386a2..fc19c8f 100644 --- a/src/include/HermesStringView.h +++ b/src/include/HermesStringView.h @@ -6,6 +6,7 @@ #define HERMESSTRINGVIEW_H #include +#include #ifdef __cplusplus extern "C" { diff --git a/test/BoostTestHermes/BoardForecastTest.cpp b/test/BoostTestHermes/BoardForecastTest.cpp deleted file mode 100644 index 9a97e1d..0000000 --- a/test/BoostTestHermes/BoardForecastTest.cpp +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) ASM Assembly Systems GmbH & Co. KG -#include "stdafx.h" - -#include "Runner.h" -#include "Sinks.h" - -using namespace Hermes; - -BOOST_AUTO_TEST_CASE(BoardForecastTest) -{ - TestCaseScope scope("BoardForecastTest"); - - std::string upstreamMachineId{"UpstreamMachineId"}; - std::string downstreamMachineId{"DownstreamMachineId"}; - - DownstreamSink downstreamSink; - Hermes::Downstream downstream(1U, downstreamSink); - Runner downstreamRunner(downstream); - - UpstreamSink upstreamSink; - Hermes::Upstream upstream(1U, upstreamSink); - Runner upstreamRunner(upstream); - - // suppress periodic CheckAlive on both sides - DownstreamSettings downstreamSettings{upstreamMachineId, 50101}; - downstreamSettings.m_checkAlivePeriodInSeconds = 0; - downstream.Enable(downstreamSettings); - - Hermes::UpstreamSettings upstreamSettings(downstreamMachineId, "127.0.0.1", 50101); - upstreamSettings.m_checkAlivePeriodInSeconds = 0; - upstream.Enable(upstreamSettings); - - // set ServiceDescription accordingly: - Hermes::ServiceDescriptionData upstreamServiceDescription{downstreamMachineId, 1U}; - upstreamServiceDescription.m_supportedFeatures.m_optionalFeatureBoardForecast = FeatureBoardForecast{}; - Hermes::ServiceDescriptionData downstreamServiceDescription{upstreamMachineId, 1U}; - downstreamServiceDescription.m_supportedFeatures.m_optionalFeatureBoardForecast = FeatureBoardForecast{}; - - // wait until connection is established - WaitFor(downstreamSink, [&]() { return downstreamSink.m_state == EState::eSOCKET_CONNECTED; }); - WaitFor(upstreamSink, [&]() { return upstreamSink.m_state == EState::eSOCKET_CONNECTED; }); - upstream.Signal(upstreamSink.m_sessionId, upstreamServiceDescription); - WaitFor(downstreamSink, [&]() { return downstreamSink.m_state == EState::eSERVICE_DESCRIPTION_DOWNSTREAM; }); - downstream.Signal(downstreamSink.m_sessionId, downstreamServiceDescription); - - WaitFor(downstreamSink, [&]() { return downstreamSink.m_state == EState::eNOT_AVAILABLE_NOT_READY; }); - WaitFor(upstreamSink, [&]() { return upstreamSink.m_state == EState::eNOT_AVAILABLE_NOT_READY; }); - - // verify ServiceDescription: - BOOST_TEST(downstreamSink.m_serviceDescription == upstreamServiceDescription); - BOOST_TEST(upstreamSink.m_serviceDescription == downstreamServiceDescription); - - // make sure, we have no data to start with: - BOOST_TEST(!upstreamSink.m_boardForecastData.m_optionalBoardId); - - // Respond with SendBoardInfo from upstream to downstream - BoardForecastData forecast{EBoardQuality::eGOOD, EFlippedBoard::eBOTTOM_SIDE_IS_UP}; - forecast.m_optionalTopBarcode = "Top"; - forecast.m_optionalBottomBarcode = "Bottom"; - forecast.m_optionalBoardId = "BoardId"; - forecast.m_optionalBoardIdCreatedBy = "CreatedBy"; - - downstream.Signal(downstreamSink.m_sessionId, forecast); - WaitFor(upstreamSink, [&]() { return upstreamSink.m_boardForecastData.m_optionalTopBarcode.has_value(); }); - BOOST_TEST(upstreamSink.m_boardForecastData == forecast); - -} diff --git a/test/BoostTestHermes/BoostTestHermes.vcxproj b/test/BoostTestHermes/BoostTestHermes.vcxproj deleted file mode 100644 index 609346e..0000000 --- a/test/BoostTestHermes/BoostTestHermes.vcxproj +++ /dev/null @@ -1,241 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - {B4CB35B8-B453-4B1E-8469-5A8059F2CE25} - SAK - SAK - SAK - SAK - Win32Proj - BoostTestHermes - 10.0 - - - - Application - true - v143 - NotSet - - - Application - false - v143 - true - NotSet - - - Application - true - v143 - NotSet - - - Application - false - v143 - true - NotSet - - - - - - - - - - - - - - - - - - - - - ..\..\bin\$(PlatformTarget)\$(Configuration)\ - ..\..\..\tmp\$(ProjectName)_$(Configuration)_$(PlatformTarget)\ - false - - - ..\..\bin\$(PlatformTarget)\$(Configuration)\ - ..\..\..\tmp\$(ProjectName)_$(Configuration)_$(PlatformTarget)\ - false - - - false - ..\..\bin\$(PlatformTarget)\$(Configuration)\ - ..\..\..\tmp\$(ProjectName)_$(Configuration)_$(PlatformTarget)\ - - - false - ..\..\bin\$(PlatformTarget)\$(Configuration)\ - ..\..\..\tmp\$(ProjectName)_$(Configuration)_$(PlatformTarget)\ - - - - Use - Level4 - Disabled - _WINDOWS;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - $(ProjectDir)\..\..\References; $(ProjectDir)\..\..\src\include; $(ProjectDir)\..\..\ - stdcpp20 - ProgramDatabase - - - Console - true - ..\..\References\lib32;..\..\..\..\lib\$(Configuration)\$(PlatformTarget)\ - Rpcrt4.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - - - $(TargetPath) --run_test="*" --detect_memory_leaks=1 - - - - - Use - Level4 - Disabled - _WINDOWS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - $(ProjectDir)\..\..\References;$(ProjectDir)\..\..\src\include;$(ProjectDir)\..\..\ - stdcpp20 - ProgramDatabase - - - Console - true - ..\..\References\lib64;..\..\..\..\lib\$(Configuration)\$(PlatformTarget)\ - Rpcrt4.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - - - $(TargetPath) --run_test="*" --detect_memory_leaks=1 - - - - - Level4 - Use - MaxSpeed - true - true - _WINDOWS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - $(ProjectDir)\..\..\References; $(ProjectDir)\..\..\src\include; $(ProjectDir)\..\..\ - stdcpp20 - - - Console - true - true - true - Rpcrt4.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - ..\..\References\lib32;..\..\..\..\lib\$(Configuration)\$(PlatformTarget)\ - - - $(TargetPath) --run_test="*" --detect_memory_leaks=1 - - - - - Level4 - Use - MaxSpeed - true - true - _WINDOWS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - $(ProjectDir)\..\..\References;$(ProjectDir)\..\..\src\include;$(ProjectDir)\..\..\ - stdcpp20 - - - Console - true - true - true - ..\..\References\lib64;..\..\..\..\lib\$(Configuration)\$(PlatformTarget)\ - Rpcrt4.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - - - $(TargetPath) --run_test="*" --detect_memory_leaks=1 - - - - - - - - - - - - - - - - - - - - - - - Create - Create - Create - Create - - - NotUsing - NotUsing - - - - - NotUsing - NotUsing - - - - - - - - - - - - - - - - - - - - {a52420c5-d236-47b7-883e-2166e83c2f43} - - - - - - \ No newline at end of file diff --git a/test/BoostTestHermes/CMakeLists.txt b/test/BoostTestHermes/CMakeLists.txt new file mode 100644 index 0000000..1ee9cfd --- /dev/null +++ b/test/BoostTestHermes/CMakeLists.txt @@ -0,0 +1,25 @@ +# ============================================================================== +# test/BoostTestHermes/CMakeLists.txt +# ============================================================================== + +# The test suite specifically requires the Boost Unit Test Framework +find_package(Boost 1.66 REQUIRED COMPONENTS unit_test_framework) + +# Gather all C++ test files +file(GLOB TEST_SOURCES "*.cpp") + +# Create the test executable +add_executable(BoostTestHermes ${TEST_SOURCES}) + +# Include local references for tests if they need direct access to pugixml or Boost headers +target_include_directories(BoostTestHermes PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../References) + +# Link the test executable against our newly built hermes library AND Boost Test +target_link_libraries(BoostTestHermes + PRIVATE + hermes + Boost::unit_test_framework +) + +# Register the executable with CTest so the `ctest` command works automatically +add_test(NAME BoostTestHermes COMMAND BoostTestHermes) \ No newline at end of file diff --git a/test/BoostTestHermes/Makefile b/test/BoostTestHermes/Makefile deleted file mode 100644 index fcaa6cd..0000000 --- a/test/BoostTestHermes/Makefile +++ /dev/null @@ -1,35 +0,0 @@ -PROGRAM = BoostTestHermes - -INCLUDEDIRS = -I. -I../../src/include - - -LIBDIRS = -L/usr/lib -L../../Hermes/.libs - -LIBS = -lhermes -lpthread - -CXXSOURCES = CApiTest.cpp ConfigurationTest.cpp DownstreamTest.cpp HermesDataTest.cpp SerializerTest.cpp TestMain.cpp Trace.cpp UpstreamTest.cpp - - -DBGFLAGS = -O2 -g0 -DNDEBUG -ifeq ($(DEBUG),1) -DBGFLAGS = -O0 -g3 -D_DEBUG -endif - -CXXOBJECTS = $(CXXSOURCES:.cpp=.o) -CXXFLAGS = -std=c++17 -Wall -DESRI_UNIX $(INCLUDEDIRS) $(DBGFLAGS) -CXX = g++ - -LDFLAGS = $(LIBDIRS) $(LIBS) - -all: $(PROGRAM) - -$(PROGRAM): $(CXXOBJECTS) - $(CXX) -o $@ $(CXXOBJECTS) $(LDFLAGS) - -clean: - $(RM) -f $(CXXOBJECTS) $(PROGRAM) - -run: - ./$(PROGRAM) - - diff --git a/test/BoostTestHermes/Makefile.mingw b/test/BoostTestHermes/Makefile.mingw deleted file mode 100644 index e2991a8..0000000 --- a/test/BoostTestHermes/Makefile.mingw +++ /dev/null @@ -1,21 +0,0 @@ -PROGRAM = BoostTestHermes.exe -INCLUDES = -I. -I../../src/include -I../../References -LIBDIRS = -L../../src/Hermes -L../../References/boost_libs -LIBS = -lHermes -lboost_unit_test_framework-mgw -lws2_32 -lmswsock -static-libgcc -static-libstdc++ - -SOURCES = CApiTest.cpp ConfigurationTest.cpp DownstreamTest.cpp HermesDataTest.cpp SerializerTest.cpp TestMain.cpp Trace.cpp UpstreamTest.cpp -OBJECTS = $(SOURCES:.cpp=.o) -CXXFLAGS = -std=c++17 -O2 -Wall -D_WINDOWS $(INCLUDES) - -all: $(PROGRAM) - -$(PROGRAM): $(OBJECTS) - g++ -o $@ $(OBJECTS) $(LIBDIRS) $(LIBS) - -%.o: %.cpp - g++ $(CXXFLAGS) -c $< -o $@ - -clean: - del /Q *.o $(PROGRAM) 2>nul - -.PHONY: all clean \ No newline at end of file From 1d3f062eaced9e0eb227ca86d18467279539c35f Mon Sep 17 00:00:00 2001 From: Sahil Agarwal Date: Wed, 25 Mar 2026 15:01:48 +0530 Subject: [PATCH 03/14] modern hermes fix --- README.md | 1003 +++++++++++++++++-- docs/01_Architecture_Overview.md | 45 - docs/02_Building_and_Linking.md | 52 - docs/03_Modern_API_Basics.md | 55 - docs/04_Downstream_Receiver.md | 0 docs/05_Upstream_Sender.md | 72 -- docs/06_Core_Message_Types.md | 72 -- docs/07_Network_and_Topology.md | 67 -- docs/08_Legacy_Interface_API.md | 93 -- docs/09_Vertical_MES_Integration.md | 86 -- docs/BUILDING.md | 100 -- examples/interactive_sender.cpp | 75 -- examples/machine_b_pnp.cpp | 68 -- examples/machine_c_oven.cpp | 41 - examples/rpi1_upstream.cpp | 206 ++-- examples/rpi2_downstream.cpp | 209 ++-- src/include/HermesDataConversion.hpp | 1392 ++++++++------------------ src/include/HermesModern.hpp | 811 +++++++-------- src/include/HermesOptional.hpp | 92 +- src/include/HermesStringView.h | 25 +- src/include/HermesStringView.hpp | 113 +-- 21 files changed, 1875 insertions(+), 2802 deletions(-) delete mode 100644 docs/01_Architecture_Overview.md delete mode 100644 docs/02_Building_and_Linking.md delete mode 100644 docs/03_Modern_API_Basics.md delete mode 100644 docs/04_Downstream_Receiver.md delete mode 100644 docs/05_Upstream_Sender.md delete mode 100644 docs/06_Core_Message_Types.md delete mode 100644 docs/07_Network_and_Topology.md delete mode 100644 docs/08_Legacy_Interface_API.md delete mode 100644 docs/09_Vertical_MES_Integration.md delete mode 100644 docs/BUILDING.md delete mode 100644 examples/interactive_sender.cpp delete mode 100644 examples/machine_b_pnp.cpp delete mode 100644 examples/machine_c_oven.cpp diff --git a/README.md b/README.md index 2a90fba..d54faca 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,912 @@ -Hermes C++ Library (lib_cpp) -The Hermes Standard (IPC-HERMES-9852) is the modern, non-proprietary TCP/IP and XML-based successor to the legacy SMEMA standard. It enables vendor-independent machine-to-machine communication in SMT assembly lines. This repository provides the official, high-performance C++ implementation of the protocol. +# Hermes C++ Library — Complete Reference + +**Protocol:** The Hermes Standard — vendor-independent machine-to-machine communication for SMT assembly lines +**License:** Apache 2.0 +**Requires:** C++17, Boost 1.66+, CMake 3.15+ + +--- + +## Table of Contents + +1. [What is Hermes](#1-what-is-hermes) +2. [Architecture overview](#2-architecture-overview) +3. [Building the library](#3-building-the-library) +4. [Header map — what to include](#4-header-map) +5. [Core types reference](#5-core-types-reference) +6. [Modern C++ API — HermesModern.hpp](#6-modern-c-api) +7. [Low-level C++ API — Hermes.hpp](#7-low-level-c-api) +8. [Serialization API — HermesSerialization.hpp](#8-serialization-api) +9. [Configuration service](#9-configuration-service) +10. [Vertical interface](#10-vertical-interface) +11. [Complete examples](#11-complete-examples) +12. [Bugs fixed in this version](#12-bugs-fixed) + +--- + +## 1. What is Hermes + +Hermes is an open TCP/IP + XML protocol that connects machines in an electronics assembly line. Each machine has an **Upstream** port (faces the previous machine) and a **Downstream** port (faces the next machine). PCB boards flow from Upstream to Downstream along the lane. + +``` +[Machine A] --downstream:50100--> [Machine B] --downstream:50101--> [Machine C] + <--upstream:50100--- <--upstream:50101--- +``` + +The Downstream machine **listens** on a port. The Upstream machine **connects** to it. So: + +- `Hermes::Downstream` / `Modern::Downstream` — **server role**, listens for incoming connections +- `Hermes::Upstream` / `Modern::Upstream` — **client role**, connects to the downstream machine + +--- + +## 2. Architecture overview -1. Core Repository Map -To navigate the library effectively, refer to the following directory structure: - -src/include/: The Public API. This directory contains the headers required to integrate Hermes into your application. - -Hermes.hpp: The primary Object-Oriented C++ wrapper. - -Hermes.h: The low-level C-style API. - -src/Hermes/: The Implementation. Contains the core logic, including the ASIO networking stack, XML serialization, and the standard-compliant state machines. - -test/BoostTestHermes/: Verification Suite. Contains comprehensive unit and integration tests. This is the source of truth for protocol-compliant behavior. - -References/: External Headers. Contains local versions of pugixml and boost headers necessary for compilation in restricted environments. - -2. Technical Requirements -Dependencies -C++ Standard: C++17 or higher. - -Networking: Boost.ASIO (v1.66 through v1.78 recommended). - -Note: Versions 1.87+ require the -DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT flag. - -XML Processing: pugixml (included in References/). - -Platform Independence -The library is designed to be fully platform-independent. It supports: - -Windows: MSVC (2017+) and MinGW-w64. - -Linux: GCC (7+) and Clang. - -Build System: The project utilizes CMake (3.15+) to generate native build files (Visual Studio Solutions or Makefiles) for your specific platform. - -3. Unified Build Process (CMake) -To build the library on any platform, use the following standard workflow: - -Generate: mkdir build && cd build && cmake .. - -Build: cmake --build . --config Release - -Outputs: - -hermes.dll / hermes.lib (Windows) - -libhermes.so (Linux) - -4. API Architecture & Design -The Callback Model -The C++ API follows a strict Interface-Based Callback pattern. - -Users must implement classes inheriting from IDownstreamCallback (Receivers) or IUpstreamCallback (Senders). - -All pure virtual methods in these interfaces must be overridden to handle protocol events (Connection, Disconnection, Board Available, Machine Ready, etc.). - -Execution Model -Blocking Event Loop: The core processing occurs within the Run() method. - -Threading: Because Run() blocks the calling thread to process network I/O, it must be executed in a dedicated background thread to maintain application responsiveness. - -State Management: The library handles all internal state transitions (e.g., Not Connected -> Service Description -> Not Ready -> Ready) automatically based on the Enable() configuration. - -5. Supported Communication Channels -The library implements the four primary channels defined by IPC-HERMES-9852: - -Upstream: Machine-to-Machine (Sending). - -Downstream: Machine-to-Machine (Receiving). - -Configuration: Exchange of machine capabilities and line settings. - -Vertical: Communication with factory-level MES/ERP systems. - -6. Deployment Topologies -The library supports standard IPv4 networking across various hardware setups: - -Point-to-Point: Direct Ethernet connection between two machines. - -Switched Fabric: Deployment via factory-wide network switches. This is the preferred method for modern "Smart Factory" environments, as it allows a single network interface to handle both horizontal (machine) and vertical (MES) data simultaneously. - -7. Quality Assurance -All protocol features are verified against the BoostTestHermes suite. - -Unit Tests: Verify individual XML serialization and data structures. - -Integration Tests: Simulate full handshakes between virtual Upstream and Downstream sessions. - -Automation: Compatible with ctest for continuous integration workflows. - -License: Copyright (c) ASM Assembly Systems GmbH & Co. KG. Licensed under the Apache License, Version 2.0. See COPYRIGHT.txt for details. \ No newline at end of file +``` +Your application + | + |--- HermesModern.hpp (std::function callbacks — recommended) + |--- Hermes.hpp (virtual interface callbacks — advanced) + |--- HermesSerialization (XML serialize/deserialize) + | + +---> Hermes C API (Hermes.h / HermesData.h) + | compiled into libhermes.so + | + +---> Boost.Asio (networking) + +---> pugixml (XML parsing) +``` + +### Message flow — horizontal (machine to machine) + +``` +Downstream machine Upstream machine +(Modern::Downstream) (Modern::Upstream) + | | + |<-- TCP connect -------------------- | + | | + |<-- ServiceDescription ------------- | (Upstream identifies itself) + |--> ServiceDescription -----------> | (Downstream identifies itself) + | | + |<-- MachineReady ------------------- | (Upstream ready to receive) + |--> BoardAvailable --------------> | (Downstream has a board) + | | + |<-- StartTransport ----------------- | (Upstream says: send it) + | [board physically moves] | + |--> TransportFinished -----------> | (Downstream confirms done) + | | + |<-- StopTransport ---------------- | (Upstream confirms received) +``` + +### Who receives what + +| You create | You listen for (receive) | You send | +|-------------------|-------------------------------------------------------------------|-------------------------------------------------------| +| `Modern::Downstream` | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | +| `Modern::Upstream` | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | + +--- + +## 3. Building the library + +### Prerequisites + +```bash +# Debian / Ubuntu / Raspberry Pi OS +sudo apt install -y g++ cmake libboost-all-dev + +# macOS +brew install cmake boost + +# Windows +# Install Boost via vcpkg: vcpkg install boost +``` + +### Build + +```bash +git clone https://github.com/hermes-org/lib_cpp.git +cd lib_cpp +mkdir build && cd build +cmake .. +make -j4 +``` + +This produces `build/src/Hermes/libhermes.so` (Linux/macOS) or `hermes.dll` (Windows). + +### Compile your application + +```bash +g++ -std=c++17 -o myapp myapp.cpp \ + -I./src/include \ + -L./build/src/Hermes -lhermes \ + -lboost_system -lpthread + +# Run (Linux — tell the linker where to find the .so) +LD_LIBRARY_PATH=./build/src/Hermes ./myapp +``` + +--- + +## 4. Header map + +| Header | What it gives you | When to include it | +|--------|------------------|--------------------| +| `HermesModern.hpp` | `Modern::Downstream`, `Modern::Upstream` with std::function callbacks | **Start here. Recommended for all new code.** | +| `Hermes.hpp` | `Hermes::Downstream`, `Hermes::Upstream`, virtual callback interfaces | When you need session IDs, raw XML, or fine-grained control | +| `HermesData.hpp` | All C++ data structs, enums, settings structs | Included automatically by the above | +| `HermesSerialization.hpp` | `ToXml()`, `FromXml()` | When you need to inspect or log raw XML messages | +| `HermesOptional.hpp` | `Hermes::Optional` = `std::optional` | Included automatically | +| `HermesStringView.hpp` | `Hermes::StringView` = `std::string_view` | Included automatically | + +--- + +## 5. Core types reference + +### EState — connection state machine + +```cpp +enum class EState { + eNOT_CONNECTED, // No TCP connection + eSOCKET_CONNECTED, // TCP connected, waiting for ServiceDescription + eSERVICE_DESCRIPTION_DOWNSTREAM, // ServiceDescription exchanged + eNOT_AVAILABLE_NOT_READY, // Connected, neither side ready + eBOARD_AVAILABLE, // Downstream has a board waiting + eMACHINE_READY, // Upstream is ready to receive + eAVAILABLE_AND_READY, // Both sides ready — transport can start + eTRANSPORTING, // Board is moving + eTRANSPORT_STOPPED, // Transport paused + eTRANSPORT_FINISHED, // Board delivered + eDISCONNECTED // Connection closed +}; +``` + +### ETraceType — log levels + +```cpp +enum class ETraceType { + eSENT, // Raw XML sent over the wire + eRECEIVED, // Raw XML received + eDEBUG, + eINFO, + eWARNING, + eERROR +}; +``` + +### Error + +```cpp +struct Error { + EErrorCode m_code; // eSUCCESS, eNETWORK_ERROR, eTIMEOUT, etc. + std::string m_text; // Human-readable description + + explicit operator bool() const; // true if error (code != eSUCCESS) +}; +``` + +### ConnectionInfo + +```cpp +struct ConnectionInfo { + std::string m_address; // Remote IP address + uint16_t m_port; // Remote port + std::string m_hostName; // Remote hostname (if resolvable) +}; +``` + +### ServiceDescriptionData + +Exchanged in both directions at connection start. Identifies the machine. + +```cpp +struct ServiceDescriptionData { + std::string m_machineId; // Required. Your machine identifier. + uint32_t m_laneId; // Required. Which lane (1-based). + Optional m_optionalInterfaceId; // Optional. Interface label. + std::string m_version; // Protocol version string. +}; +``` + +### BoardAvailableData + +Sent by Downstream to signal a board is ready for transfer. + +```cpp +struct BoardAvailableData { + std::string m_boardId; // Required. Unique board identifier. + std::string m_boardIdCreatedBy; // Required. Machine that created the ID. + EBoardQuality m_failedBoard; // eANY, eGOOD, eBAD + Optional m_optionalProductTypeId; + EFlippedBoard m_flippedBoard; // eSIDE_UP_IS_UNKNOWN, eTOP_SIDE_IS_UP, eBOTTOM_SIDE_IS_UP + Optional m_optionalTopBarcode; + Optional m_optionalBottomBarcode; + Optional m_length; // mm + Optional m_width; // mm + Optional m_thickness; // mm + Optional m_conveyorSpeed; // mm/s + Optional m_topClearanceHeight; // mm + Optional m_bottomClearanceHeight; // mm + Optional m_weight; // grams + Optional m_optionalWorkOrderId; + Optional m_optionalBatchId; + Optional m_optionalRoute; + Optional m_optionalAction; + std::vector m_subBoards; +}; +``` + +### MachineReadyData + +Sent by Upstream to signal it is ready to receive a board. + +```cpp +struct MachineReadyData { + EBoardQuality m_failedBoard; // What quality board it can accept + Optional m_optionalForecastId; + Optional m_optionalBoardId; + Optional m_optionalProductTypeId; + Optional m_optionalFlippedBoard; + Optional m_optionalTopBarcode; + Optional m_optionalBottomBarcode; + Optional m_length; + Optional m_width; + Optional m_thickness; + Optional m_conveyorSpeed; + Optional m_topClearanceHeight; + Optional m_bottomClearanceHeight; + Optional m_weight; + Optional m_optionalWorkOrderId; + Optional m_optionalBatchId; +}; +``` + +### StartTransportData / StopTransportData / TransportFinishedData + +```cpp +struct StartTransportData { + std::string m_boardId; // Which board to transport + Optional m_conveyorSpeed; // mm/s, optional override +}; + +struct StopTransportData { + ETransferState m_transferState; // eCOMPLETE, eINCOMPLETE, eNOT_STARTED + std::string m_boardId; +}; + +struct TransportFinishedData { + ETransferState m_transferState; + std::string m_boardId; +}; +``` + +### NotificationData + +Sent by either side to report errors or status. + +```cpp +struct NotificationData { + ENotificationCode m_notificationCode; // ePROTOCOL_ERROR, eMACHINE_SHUTDOWN, etc. + ESeverity m_severity; // eFATAL, eERROR, eWARNING, eINFO + std::string m_description; // Human-readable message +}; +``` + +### Settings structs + +```cpp +struct DownstreamSettings { + std::string m_machineId; // Required + Optional m_optionalClientAddress; // Restrict to one client IP + uint16_t m_port{0}; // 0 = use cBASE_PORT (50100) + double m_checkAlivePeriodInSeconds{60}; + double m_reconnectWaitTimeInSeconds{10}; + ECheckAliveResponseMode m_checkAliveResponseMode{eAUTO}; + ECheckState m_checkState{eSEND_AND_RECEIVE}; +}; + +struct UpstreamSettings { + std::string m_machineId; // Required + std::string m_hostAddress; // Required. IP of the downstream machine. + uint16_t m_port{0}; // 0 = use cBASE_PORT (50100) + double m_checkAlivePeriodInSeconds{60}; + double m_reconnectWaitTimeInSeconds{10}; + ECheckAliveResponseMode m_checkAliveResponseMode{eAUTO}; + ECheckState m_checkState{eSEND_AND_RECEIVE}; +}; +``` + +--- + +## 6. Modern C++ API + +`HermesModern.hpp` provides `Modern::Downstream` and `Modern::Upstream`. These wrap the low-level virtual callback interface with `std::function` callbacks. This is the recommended API for new code. + +### Modern::Downstream + +```cpp +Hermes::Modern::Downstream ds(unsigned laneId); +``` + +**Register callbacks:** + +```cpp +// Connection events — sessionId identifies the active connection +ds.RegisterConnectedCallback( [](unsigned sessionId, const ConnectionInfo& info) { ... }); +ds.RegisterDisconnectedCallback([](unsigned sessionId, const Error& err) { ... }); +ds.RegisterStateChangeCallback( [](unsigned sessionId, EState state) { ... }); +ds.RegisterTraceCallback( [](unsigned sessionId, ETraceType type, StringView msg) { ... }); + +// Messages received FROM the Upstream machine +ds.RegisterServiceDescriptionCallback( [](unsigned sessionId, EState, const ServiceDescriptionData& d) { ... }); +ds.RegisterMachineReadyCallback( [](unsigned sessionId, EState, const MachineReadyData& d) { ... }); +ds.RegisterRevokeMachineReadyCallback( [](unsigned sessionId, EState, const RevokeMachineReadyData& d) { ... }); +ds.RegisterStartTransportCallback( [](unsigned sessionId, EState, const StartTransportData& d) { ... }); +ds.RegisterStopTransportCallback( [](unsigned sessionId, EState, const StopTransportData& d) { ... }); +ds.RegisterQueryBoardInfoCallback( [](unsigned sessionId, const QueryBoardInfoData& d) { ... }); + +// Auxiliary +ds.RegisterNotificationCallback([](unsigned sessionId, const NotificationData& d) { ... }); +ds.RegisterCheckAliveCallback( [](unsigned sessionId, const CheckAliveData& d) { ... }); +ds.RegisterCommandCallback( [](unsigned sessionId, const CommandData& d) { ... }); +``` + +**Lifecycle:** + +```cpp +DownstreamSettings settings; +settings.m_machineId = "MyMachine"; +settings.m_port = 50100; + +ds.Enable(settings); // starts listening, launches network thread +// ... application runs ... +ds.Stop(); // stops network thread, closes connection +``` + +**Send messages to the Upstream:** + +```cpp +// Must call from inside Post() or from within a callback — both are on the network thread +ds.Post([&ds, sessionId]() { + BoardAvailableData board; + board.m_boardId = "BOARD-001"; + board.m_boardIdCreatedBy = "MyMachine"; + board.m_failedBoard = EBoardQuality::eGOOD; + board.m_flippedBoard = EFlippedBoard::eTOP_SIDE_IS_UP; + ds.Signal(sessionId, board); +}); +``` + +### Modern::Upstream + +```cpp +Hermes::Modern::Upstream us(unsigned laneId); +``` + +**Register callbacks:** + +```cpp +us.RegisterConnectedCallback( [](unsigned sessionId, const ConnectionInfo& info) { ... }); +us.RegisterDisconnectedCallback([](unsigned sessionId, const Error& err) { ... }); +us.RegisterStateChangeCallback( [](unsigned sessionId, EState state) { ... }); +us.RegisterTraceCallback( [](unsigned sessionId, ETraceType type, StringView msg) { ... }); + +// Messages received FROM the Downstream machine +us.RegisterServiceDescriptionCallback( [](unsigned sessionId, EState, const ServiceDescriptionData& d) { ... }); +us.RegisterBoardAvailableCallback( [](unsigned sessionId, EState, const BoardAvailableData& d) { ... }); +us.RegisterRevokeBoardAvailableCallback( [](unsigned sessionId, EState, const RevokeBoardAvailableData& d) { ... }); +us.RegisterTransportFinishedCallback( [](unsigned sessionId, EState, const TransportFinishedData& d) { ... }); +us.RegisterBoardForecastCallback( [](unsigned sessionId, EState, const BoardForecastData& d) { ... }); +us.RegisterSendBoardInfoCallback( [](unsigned sessionId, const SendBoardInfoData& d) { ... }); + +// Auxiliary +us.RegisterNotificationCallback([](unsigned sessionId, const NotificationData& d) { ... }); +us.RegisterCheckAliveCallback( [](unsigned sessionId, const CheckAliveData& d) { ... }); +us.RegisterCommandCallback( [](unsigned sessionId, const CommandData& d) { ... }); +``` + +**Lifecycle:** + +```cpp +UpstreamSettings settings; +settings.m_machineId = "MyMachine"; +settings.m_hostAddress = "192.168.1.2"; // IP of the Downstream machine +settings.m_port = 50100; + +us.Enable(settings); +// ... +us.Stop(); +``` + +**Send messages to the Downstream:** + +```cpp +us.Post([&us, sessionId]() { + MachineReadyData ready; + ready.m_failedBoard = EBoardQuality::eANY; + us.Signal(sessionId, ready); +}); +``` + +--- + +## 7. Low-level C++ API + +`Hermes.hpp` provides `Hermes::Downstream` and `Hermes::Upstream` with virtual callback interfaces. Use this when you need the session ID in every callback, raw XML signals, or multiple sessions. + +### Implementing IDownstreamCallback + +```cpp +struct MyDownstreamHandler : Hermes::IDownstreamCallback +{ + // Pure virtuals — must implement all of these: + void OnConnected(unsigned sessionId, EState, const ConnectionInfo&) override; + void OnDisconnected(unsigned sessionId, EState, const Error&) override; + void OnState(unsigned sessionId, EState) override; + void OnTrace(unsigned sessionId, ETraceType, StringView) override; + + // Messages from Upstream — pure virtual: + void On(unsigned sessionId, EState, const ServiceDescriptionData&) override; + void On(unsigned sessionId, EState, const MachineReadyData&) override; + void On(unsigned sessionId, EState, const RevokeMachineReadyData&) override; + void On(unsigned sessionId, EState, const StartTransportData&) override; + void On(unsigned sessionId, EState, const StopTransportData&) override; + void On(unsigned sessionId, const NotificationData&) override; + void On(unsigned sessionId, const CommandData&) override; + + // Optional — have default empty implementations: + void On(unsigned sessionId, const CheckAliveData&) override {} + void On(unsigned sessionId, const QueryBoardInfoData&) override {} +}; +``` + +**Usage:** + +```cpp +MyDownstreamHandler handler; +Hermes::Downstream downstream(1, handler); // lane 1 + +DownstreamSettings settings; +settings.m_machineId = "Machine-A"; +settings.m_port = 50100; +downstream.Enable(settings); +downstream.Run(); // blocks until Stop() is called from another thread +``` + +### Implementing IUpstreamCallback + +```cpp +struct MyUpstreamHandler : Hermes::IUpstreamCallback +{ + void OnConnected(unsigned sessionId, EState, const ConnectionInfo&) override; + void OnDisconnected(unsigned sessionId, EState, const Error&) override; + void OnState(unsigned sessionId, EState) override; + void OnTrace(unsigned sessionId, ETraceType, StringView) override; + + // Messages from Downstream — pure virtual: + void On(unsigned sessionId, EState, const ServiceDescriptionData&) override; + void On(unsigned sessionId, EState, const BoardAvailableData&) override; + void On(unsigned sessionId, EState, const RevokeBoardAvailableData&) override; + void On(unsigned sessionId, EState, const TransportFinishedData&) override; + void On(unsigned sessionId, const NotificationData&) override; + void On(unsigned sessionId, const CommandData&) override; + + // Optional: + void On(unsigned sessionId, const CheckAliveData&) override {} + void On(unsigned sessionId, EState, const BoardForecastData&) override {} + void On(unsigned sessionId, const SendBoardInfoData&) override {} +}; +``` + +### Sending signals from within callbacks + +Always use `Post()` to send signals — it schedules the send on the Hermes network thread safely: + +```cpp +void On(unsigned sessionId, EState, const MachineReadyData&) override +{ + // DO NOT call Signal() directly here in the virtual callback API. + // Use Post() to dispatch onto the network thread: + m_downstream->Post([this, sessionId]() { + BoardAvailableData board; + board.m_boardId = "B-001"; + board.m_boardIdCreatedBy = "MachineA"; + board.m_failedBoard = EBoardQuality::eGOOD; + board.m_flippedBoard = EFlippedBoard::eTOP_SIDE_IS_UP; + m_downstream->Signal(sessionId, board); + }); +} +``` + +> In `Modern::Downstream` / `Modern::Upstream`, callbacks already run on the network thread, so you can call `Signal()` directly inside them. + +--- + +## 8. Serialization API + +`HermesSerialization.hpp` lets you convert any data struct to XML and back. Useful for logging, testing, or debugging. + +```cpp +#include "HermesSerialization.hpp" + +// Serialize to XML string +BoardAvailableData board; +board.m_boardId = "BOARD-001"; +board.m_boardIdCreatedBy = "Machine-A"; +board.m_failedBoard = EBoardQuality::eGOOD; +board.m_flippedBoard = EFlippedBoard::eTOP_SIDE_IS_UP; + +std::string xml = Hermes::ToXml(board); +// xml = "BOARD-001..." + +// Deserialize from XML string +Hermes::Optional result = Hermes::FromXml(xml); +if (result.has_value()) { + std::cout << result->m_boardId << "\n"; // "BOARD-001" +} +``` + +All message types are supported: + +```cpp +Hermes::ToXml(const ServiceDescriptionData&) +Hermes::ToXml(const BoardAvailableData&) +Hermes::ToXml(const RevokeBoardAvailableData&) +Hermes::ToXml(const MachineReadyData&) +Hermes::ToXml(const RevokeMachineReadyData&) +Hermes::ToXml(const StartTransportData&) +Hermes::ToXml(const StopTransportData&) +Hermes::ToXml(const TransportFinishedData&) +Hermes::ToXml(const BoardForecastData&) +Hermes::ToXml(const QueryBoardInfoData&) +Hermes::ToXml(const SendBoardInfoData&) +Hermes::ToXml(const NotificationData&) +Hermes::ToXml(const CheckAliveData&) +Hermes::ToXml(const SetConfigurationData&) +Hermes::ToXml(const CurrentConfigurationData&) +Hermes::ToXml(const GetConfigurationData&) +Hermes::ToXml(const CommandData&) + +// Vertical: +Hermes::ToXml(const SupervisoryServiceDescriptionData&) +Hermes::ToXml(const BoardArrivedData&) +Hermes::ToXml(const BoardDepartedData&) +Hermes::ToXml(const QueryWorkOrderInfoData&) +Hermes::ToXml(const SendWorkOrderInfoData&) +Hermes::ToXml(const ReplyWorkOrderInfoData&) +Hermes::ToXml(const QueryHermesCapabilitiesData&) +Hermes::ToXml(const SendHermesCapabilitiesData&) +``` + +--- + +## 9. Configuration service + +The configuration service allows a remote tool (e.g. a factory MES) to read and write the machine's lane configuration over TCP on port 1248. + +```cpp +#include "Hermes.hpp" + +struct MyConfigHandler : Hermes::IConfigurationServiceCallback +{ + void OnConnected(unsigned sessionId, const ConnectionInfo& info) override + { + std::cout << "Config client connected: " << info.m_address << "\n"; + } + + // Remote client reads our configuration + CurrentConfigurationData OnGetConfiguration(unsigned, const ConnectionInfo&) override + { + CurrentConfigurationData config; + config.m_optionalMachineId = "Machine-A"; + + UpstreamConfiguration up; + up.m_upstreamLaneId = 1; + up.m_hostAddress = "192.168.1.2"; + up.m_port = 50100; + config.m_upstreamConfigurations.push_back(up); + + return config; + } + + // Remote client writes a new configuration — return Error{} for success + Error OnSetConfiguration(unsigned, const ConnectionInfo&, + const SetConfigurationData& newConfig) override + { + // Apply newConfig to your machine... + return Error{}; // success + } + + void OnDisconnected(unsigned, const Error&) override {} + void OnTrace(unsigned, ETraceType, StringView) override {} +}; + +// Usage: +MyConfigHandler handler; +Hermes::ConfigurationService service(handler); + +ConfigurationServiceSettings settings; +settings.m_port = cCONFIG_PORT; // 1248 + +service.Enable(settings); +service.Run(); // blocks +``` + +### Remote configuration client + +Query or set a machine's configuration from another process: + +```cpp +// Get configuration +auto [config, error] = Hermes::GetConfiguration("192.168.1.2", 10, nullptr); +if (!error) { + std::cout << "Machine: " << config.m_optionalMachineId.value_or("unknown") << "\n"; +} + +// Set configuration +SetConfigurationData newConfig; +newConfig.m_machineId = "Machine-A"; + +Error err = Hermes::SetConfiguration( + "192.168.1.2", // host + newConfig, // configuration to write + 10, // timeout seconds + nullptr, // optional: resulting config out + nullptr, // optional: notifications out + nullptr // optional: trace callback +); +``` + +--- + +## 10. Vertical interface + +The vertical interface connects a machine to a supervisory system (MES/ERP) on a separate TCP port. `VerticalService` is the machine side. `VerticalClient` is the supervisory system side. + +```cpp +struct MyVerticalHandler : Hermes::IVerticalServiceCallback +{ + void OnConnected(unsigned sessionId, EVerticalState, const ConnectionInfo&) override {} + + void On(unsigned sessionId, EVerticalState, + const SupervisoryServiceDescriptionData& data) override + { + std::cout << "Supervisor connected: " << data.m_systemId << "\n"; + } + + void On(unsigned sessionId, const GetConfigurationData&, const ConnectionInfo&) override {} + void On(unsigned sessionId, const SetConfigurationData&, const ConnectionInfo&) override {} + void On(unsigned sessionId, const NotificationData&) override {} + void On(unsigned sessionId, const QueryHermesCapabilitiesData&) override {} + void OnDisconnected(unsigned sessionId, EVerticalState, const Error&) override {} + void OnTrace(unsigned sessionId, ETraceType, StringView) override {} +}; +``` + +--- + +## 11. Complete examples + +### Example 1 — Downstream machine (listens, receives a board) + +```cpp +#include "HermesModern.hpp" +#include +#include +#include +#include +#include + +static std::atomic g_running{true}; + +int main() +{ + std::signal(SIGINT, [](int) { g_running = false; }); + + Hermes::Modern::Downstream ds(1); // lane 1 + + // --- Connection events --- + ds.RegisterConnectedCallback([](unsigned sessionId, const Hermes::ConnectionInfo& info) { + std::cout << "[DS] Upstream connected from " << info.m_address << "\n"; + }); + + ds.RegisterDisconnectedCallback([](unsigned sessionId, const Hermes::Error& err) { + std::cout << "[DS] Upstream disconnected\n"; + }); + + ds.RegisterStateChangeCallback([](unsigned sessionId, Hermes::EState state) { + std::cout << "[DS] State: " << state << "\n"; + }); + + // --- Receive ServiceDescription from Upstream, reply with ours --- + ds.RegisterServiceDescriptionCallback( + [&ds](unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) { + std::cout << "[DS] Received ServiceDescription from: " << data.m_machineId << "\n"; + + // Reply with our own ServiceDescription + ds.Post([&ds, sessionId]() { + Hermes::ServiceDescriptionData reply; + reply.m_machineId = "Machine-Downstream"; + reply.m_laneId = 1; + ds.Signal(sessionId, reply); + std::cout << "[DS] Sent ServiceDescription\n"; + }); + }); + + // --- When Upstream is MachineReady, send BoardAvailable --- + ds.RegisterMachineReadyCallback( + [&ds](unsigned sessionId, Hermes::EState, const Hermes::MachineReadyData&) { + std::cout << "[DS] Upstream is MachineReady — sending BoardAvailable\n"; + + ds.Post([&ds, sessionId]() { + Hermes::BoardAvailableData board; + board.m_boardId = "PCB-2024-001"; + board.m_boardIdCreatedBy = "Machine-Downstream"; + board.m_failedBoard = Hermes::EBoardQuality::eGOOD; + board.m_flippedBoard = Hermes::EFlippedBoard::eTOP_SIDE_IS_UP; + ds.Signal(sessionId, board); + }); + }); + + // --- When Upstream starts transport, confirm when done --- + ds.RegisterStartTransportCallback( + [&ds](unsigned sessionId, Hermes::EState, const Hermes::StartTransportData& data) { + std::cout << "[DS] StartTransport for board: " << data.m_boardId << "\n"; + + // Simulate board moving + std::this_thread::sleep_for(std::chrono::seconds(1)); + + ds.Post([&ds, sessionId, boardId = data.m_boardId]() { + Hermes::TransportFinishedData finished; + finished.m_transferState = Hermes::ETransferState::eCOMPLETE; + finished.m_boardId = boardId; + ds.Signal(sessionId, finished); + std::cout << "[DS] TransportFinished sent\n"; + }); + }); + + // --- Notifications --- + ds.RegisterNotificationCallback([](unsigned, const Hermes::NotificationData& n) { + std::cout << "[DS] Notification: " << n.m_description << "\n"; + }); + + // --- Start --- + Hermes::DownstreamSettings settings; + settings.m_machineId = "Machine-Downstream"; + settings.m_port = 50100; + ds.Enable(settings); + + std::cout << "[DS] Listening on port 50100. Ctrl+C to stop.\n"; + while (g_running) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + ds.Stop(); + return 0; +} +``` + +### Example 2 — Upstream machine (connects, sends MachineReady) + +```cpp +#include "HermesModern.hpp" +#include +#include +#include +#include +#include + +static std::atomic g_running{true}; + +int main() +{ + std::signal(SIGINT, [](int) { g_running = false; }); + + Hermes::Modern::Upstream us(1); // lane 1 + + us.RegisterConnectedCallback([](unsigned sessionId, const Hermes::ConnectionInfo& info) { + std::cout << "[US] Connected to downstream at " << info.m_address << "\n"; + }); + + us.RegisterDisconnectedCallback([](unsigned sessionId, const Hermes::Error& err) { + std::cout << "[US] Disconnected\n"; + }); + + us.RegisterStateChangeCallback([](unsigned sessionId, Hermes::EState state) { + std::cout << "[US] State: " << state << "\n"; + }); + + // --- Receive ServiceDescription, reply and send MachineReady --- + us.RegisterServiceDescriptionCallback( + [&us](unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) { + std::cout << "[US] Received ServiceDescription from: " << data.m_machineId << "\n"; + + us.Post([&us, sessionId]() { + // Send our ServiceDescription + Hermes::ServiceDescriptionData reply; + reply.m_machineId = "Machine-Upstream"; + reply.m_laneId = 1; + us.Signal(sessionId, reply); + + // Then send MachineReady + Hermes::MachineReadyData ready; + ready.m_failedBoard = Hermes::EBoardQuality::eANY; + us.Signal(sessionId, ready); + std::cout << "[US] Sent ServiceDescription + MachineReady\n"; + }); + }); + + // --- Receive BoardAvailable, trigger StartTransport --- + us.RegisterBoardAvailableCallback( + [&us](unsigned sessionId, Hermes::EState, const Hermes::BoardAvailableData& board) { + std::cout << "[US] BoardAvailable: " << board.m_boardId << "\n"; + + us.Post([&us, sessionId, boardId = board.m_boardId]() { + Hermes::StartTransportData start; + start.m_boardId = boardId; + us.Signal(sessionId, start); + std::cout << "[US] StartTransport sent\n"; + }); + }); + + // --- Receive TransportFinished, send StopTransport --- + us.RegisterTransportFinishedCallback( + [&us](unsigned sessionId, Hermes::EState, const Hermes::TransportFinishedData& data) { + std::cout << "[US] TransportFinished for board: " << data.m_boardId << "\n"; + + us.Post([&us, sessionId, boardId = data.m_boardId]() { + Hermes::StopTransportData stop; + stop.m_transferState = Hermes::ETransferState::eCOMPLETE; + stop.m_boardId = boardId; + us.Signal(sessionId, stop); + std::cout << "[US] StopTransport sent — full board transfer complete\n"; + }); + }); + + us.RegisterNotificationCallback([](unsigned, const Hermes::NotificationData& n) { + std::cout << "[US] Notification: " << n.m_description << "\n"; + }); + + // EDIT THIS — set the downstream machine's IP: + Hermes::UpstreamSettings settings; + settings.m_machineId = "Machine-Upstream"; + settings.m_hostAddress = "192.168.1.2"; // <-- downstream machine IP + settings.m_port = 50100; + us.Enable(settings); + + std::cout << "[US] Connecting to 192.168.1.2:50100. Ctrl+C to stop.\n"; + while (g_running) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + us.Stop(); + return 0; +} +``` + +### Example 3 — XML serialization and inspection + +```cpp +#include "HermesSerialization.hpp" +#include + +int main() +{ + // Serialize + Hermes::BoardAvailableData board; + board.m_boardId = "PCB-001"; + board.m_boardIdCreatedBy = "Machine-A"; + board.m_failedBoard = Hermes::EBoardQuality::eGOOD; + board.m_flippedBoard = Hermes::EFlippedBoard::eTOP_SIDE_IS_UP; + board.m_length = 150.0; + board.m_width = 100.0; + + std::string xml = Hermes::ToXml(board); + std::cout << "XML:\n" << xml << "\n"; + + // Deserialize + auto result = Hermes::FromXml(xml); + if (result.has_value()) { + std::cout << "BoardId: " << result->m_boardId << "\n"; + if (result->m_length.has_value()) + std::cout << "Length: " << *result->m_length << "mm\n"; + } + return 0; +} +``` \ No newline at end of file diff --git a/docs/01_Architecture_Overview.md b/docs/01_Architecture_Overview.md deleted file mode 100644 index a0533f5..0000000 --- a/docs/01_Architecture_Overview.md +++ /dev/null @@ -1,45 +0,0 @@ -# 01. Architecture Overview - -The Hermes Standard (IPC-HERMES-9852) is a protocol based on TCP/IP and XML designed to replace the legacy SMEMA wiring standard. This C++ library serves as a robust wrapper around the protocol, handling the low-level socket connections, XML serialization, and the strict state machines required for compliance. - -To effectively use this library, you must understand three core architectural concepts: **Machine Roles**, the **Event-Driven Model**, and **Thread Management**. - ---- - -## 1. Machine Roles (Network Topology) - -In a Hermes line, machines communicate horizontally (machine-to-machine) and vertically (machine-to-factory). The library provides dedicated classes for each role: - -### Horizontal Integration (The SMT Line) -* **`Downstream` (The Receiver):** Acts as a **TCP Server**. A downstream machine (e.g., a Pick & Place) opens a specific port and listens. It waits for the previous machine in the line to connect and hand over a board. -* **`Upstream` (The Sender):** Acts as a **TCP Client**. An upstream machine (e.g., a Printer) actively connects to the IP address and port of the next machine in the line to send a board. - -*Note: Most machines in the middle of a line will instantiate **both** an `Upstream` object (to send to the right) and a `Downstream` object (to receive from the left).* - -### Vertical Integration (The Factory) -* **`ConfigurationService`:** Used to exchange machine capabilities and supervisory line control data. -* **`VerticalService`:** Acts as a TCP Server to provide real-time board tracking data to higher-level MES/ERP systems (often working alongside IPC-CFX). - ---- - -## 2. The Event-Driven Model - -The library is entirely asynchronous and event-driven. You do not write code that says `WaitForBoard()`. Instead, you register **Callbacks** that the library triggers when network events occur. - -If you are using the modern wrapper (`HermesModern.hpp`), these events are handled via standard C++ lambdas: - -```cpp -// Example: The library triggers this lambda when an XML message arrives -downstream.RegisterBoardAvailableCallback([](const Hermes::BoardAvailableData& board) { - std::cout << "Board Arrived! Barcode: " << board._topBarcode << std::endl; -}); -3. Thread Management (Critical) -Because the library utilizes Boost.ASIO for high-performance networking, it relies on a continuous event loop to process incoming socket data. - -The Run() Loop: All network processing happens inside the Run() method of the Upstream or Downstream objects. - -Blocking Behavior: The Run() method is blocking. If you call it on your main application thread, your application's UI or main control loop will freeze. - -The Solution: You must always execute the core Hermes loop in a dedicated background std::thread. - -(Note: If you use the HermesModern.hpp wrapper, this background thread is automatically managed for you when you call Enable()). \ No newline at end of file diff --git a/docs/02_Building_and_Linking.md b/docs/02_Building_and_Linking.md deleted file mode 100644 index d55b451..0000000 --- a/docs/02_Building_and_Linking.md +++ /dev/null @@ -1,52 +0,0 @@ -# 02. Building and Linking - -The Hermes library uses **CMake (3.15+)** as its universal build system, ensuring seamless, cross-platform compilation on Windows, Linux, and macOS. - ---- - -## 1. System Requirements - -Before you begin, verify your environment has the following: -* **C++17 Compiler:** MSVC 2017+, GCC 7+, or Clang 5+. -* **Boost Libraries:** Version 1.66 through 1.78 is recommended. The library specifically requires `boost_system` and `boost_thread`. - * *Note on Boost 1.87+: If using a newer version of Boost where `io_service` is deprecated, our CMake setup automatically applies the `-DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT` flag to ensure it still compiles.* -* **pugixml:** No installation required. The required files are bundled locally in the `References/` folder. - ---- - -## 2. Compiling the Library - -Open a terminal in the root of the cloned repository and execute a standard out-of-source build: - -```bash -mkdir build && cd build - -# 1. Configure the project (CMake locates Boost and your compiler) -cmake .. - -# 2. Compile the core library -cmake --build . --config Release -Build Outputs: - -Windows: build/Release/hermes.dll and hermes.lib - -Linux: build/libhermes.so - -3. Linking Hermes to Your Project -The cleanest way to integrate Hermes into your own C++ application is by using CMake's add_subdirectory command. - -Assuming you cloned this repository into a folder named third_party/lib_cpp inside your project, your application's CMakeLists.txt would look like this: - -CMake -cmake_minimum_required(VERSION 3.15) -project(MySmtMachine) - -# 1. Include the Hermes library directory -add_subdirectory(third_party/lib_cpp) - -# 2. Define your application executable -add_executable(MyApp main.cpp) - -# 3. Link Hermes to your application -# This automatically configures the include paths for Hermes.hpp -target_link_libraries(MyApp PRIVATE hermes) \ No newline at end of file diff --git a/docs/03_Modern_API_Basics.md b/docs/03_Modern_API_Basics.md deleted file mode 100644 index 34b5836..0000000 --- a/docs/03_Modern_API_Basics.md +++ /dev/null @@ -1,55 +0,0 @@ -# 03. Modern API Basics - -The legacy Hermes C++ library relies on strict class inheritance (`IDownstreamCallback` and `IUpstreamCallback`) and manual thread management. To simplify development, we provide `HermesModern.hpp`, a wrapper that allows you to use modern C++17 lambdas and automatically manages the background networking threads. - ---- - -## 1. Instantiating the Node - -Every Hermes connection requires a `laneId` (typically `1` for a standard single-lane machine). You instantiate either a `Downstream` (Receiver) or `Upstream` (Sender) object from the `Hermes::Modern` namespace. - -```cpp -#include "HermesModern.hpp" - -// Create a receiver (listens for the previous machine) on Lane 1 -Hermes::Modern::Downstream receiver(1); - -// Create a sender (connects to the next machine) on Lane 1 -Hermes::Modern::Upstream sender(1); -2. Registering Callbacks -Instead of overriding pure virtual functions in a separate class, you register lambda functions directly to the object. You only need to register the callbacks your machine actually cares about. - -C++ -// Handle successful TCP connections -receiver.RegisterConnectedCallback([](const Hermes::ConnectionInfo& info) { - std::cout << "Connected to: " << info.m_hostAddress << "\n"; -}); - -// Handle incoming board data -receiver.RegisterBoardAvailableCallback([](const Hermes::BoardAvailableData& board) { - std::cout << "Incoming Board ID: " << board.m_boardId << "\n"; -}); - -// Handle disconnection or errors -receiver.RegisterDisconnectedCallback([](const Hermes::Error& err) { - std::cout << "Disconnected. Reason: " << err.m_text << "\n"; -}); -3. Configuration and Execution (Enable) -To start listening (Downstream) or actively connecting (Upstream), you must populate a settings object and pass it to the Enable() method. - -Crucially, calling Enable() in the modern wrapper automatically spawns the required background std::thread to process network events. You do not need to manage the blocking Run() loop yourself. - -C++ -Hermes::DownstreamSettings settings; -settings.m_machineId = "My_Pick_And_Place"; -settings.m_clientAddress = "192.168.1.100"; // Accept connections from this IP -settings.m_port = 50101; // Standard Hermes Default Port - -// Start the network event loop in the background -receiver.Enable(settings); -4. Shutting Down (Stop) -When your application is closing, or if you need to sever the connection to change line configurations, call Stop(). This will cleanly disconnect the socket, send the appropriate XML termination messages, and safely join the background thread. - -C++ -// Safely shut down the connection and stop the background thread -receiver.Stop(); \ No newline at end of file diff --git a/docs/04_Downstream_Receiver.md b/docs/04_Downstream_Receiver.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/05_Upstream_Sender.md b/docs/05_Upstream_Sender.md deleted file mode 100644 index fda4ee4..0000000 --- a/docs/05_Upstream_Sender.md +++ /dev/null @@ -1,72 +0,0 @@ -# 05. Upstream Sender (The Handshake) - -An **Upstream** node acts as the sending machine (e.g., a printer sending a board to a pick-and-place). It operates as a TCP Client, actively attempting to connect to the IP address and port of the next machine in the line. - -To successfully hand over a board, you must follow the exact reverse of the Downstream handshake. - ---- - -## 1. The Standard Handshake Sequence - -If you send messages out of sequence, the receiving machine is required by the Hermes Standard to drop the connection. - -| Upstream (Sender - YOU) | Direction | Downstream (Receiver) | -| :--- | :---: | :--- | -| `ServiceDescription` | ➔ | *(Establish protocol version)* | -| | ⬅ | `MachineReady` *(I have space for a board)* | -| `BoardAvailable` | ➔ | *(Contains Barcode/Dimensions)* | -| | ⬅ | `StartTransport` *(Conveyors turning, send it!)* | -| `TransportFinished`| ➔ | *(Board has left my machine)* | -| | ⬅ | `StopTransport` *(Board is fully inside me)* | - ---- - -## 2. Implementing the Sender Logic - -Using the `HermesModern.hpp` wrapper, here is how you implement a compliant sender. - -*(Note: In a real machine application, you will also integrate your physical sensors—like board edge detectors—into this logic flow).* - -```cpp -#include "HermesModern.hpp" -#include - -Hermes::Modern::Upstream sender(1); -const unsigned CURRENT_SESSION = 1; - -// 1. Wait for the Downstream machine to say it is empty -sender.RegisterMachineReadyCallback([&](const Hermes::MachineReadyData& data) { - std::cout << "Downstream is ready to receive a board.\n"; - - // 2. When your machine finishes processing a board, announce it - Hermes::BoardAvailableData boardData; - boardData.m_boardId = "PCB_12345"; - boardData.m_topBarcode = "SN-998877"; - boardData.m_lengthInMM = 150.0f; - boardData.m_widthInMM = 100.0f; - sender.Signal(CURRENT_SESSION, boardData); -}); - -// 3. Downstream has started its conveyors and says "Send it" -sender.RegisterStartTransportCallback([&](const Hermes::StartTransportData& data) { - std::cout << "Downstream conveyors running at " << data.m_conveyorSpeed << " mm/s\n"; - - // 4. Start YOUR conveyors to physically move the board out. - // ... (Wait for hardware sensor to confirm board left the machine) ... - - // 5. Tell the Downstream machine the board is fully on their side - Hermes::TransportFinishedData finishedData; - finishedData.m_transferState = Hermes::ETransferState::eCOMPLETE; - sender.Signal(CURRENT_SESSION, finishedData); -}); - -// 6. Downstream confirms the board has safely arrived inside -sender.RegisterStopTransportCallback([&](const Hermes::StopTransportData& data) { - std::cout << "Handover Complete! Board safely delivered.\n"; - - // You are now free to process the next board. -}); -3. Handling Exceptions -RevokeMachineReady: If you receive this before sending BoardAvailable, it means the downstream machine suddenly became unavailable (e.g., an operator hit the Emergency Stop). You must wait until you receive a new MachineReady message before trying to send your board. - -Connection Loss: If the connection drops during transport, halt your conveyors immediately. \ No newline at end of file diff --git a/docs/06_Core_Message_Types.md b/docs/06_Core_Message_Types.md deleted file mode 100644 index 376b066..0000000 --- a/docs/06_Core_Message_Types.md +++ /dev/null @@ -1,72 +0,0 @@ -# 06. Core Message Types & Data Dictionary - -The Hermes Standard defines specific payloads for every stage of the handshake. This document provides a quick reference for the exact C++ structs and fields you will interact with in `HermesData.hpp`. - -## ⚠️ Important Note on `Hermes::Optional` -To maintain compatibility with older C++ compilers, this library does not use `std::optional`. It uses a custom `Hermes::Optional`. To safely read optional fields like barcodes or dimensions, check them like a boolean and dereference them with `*`: - -```cpp -if (boardData.m_optionalTopBarcode) { - std::string barcode = *boardData.m_optionalTopBarcode; -} -1. Initialization Messages -ServiceDescriptionData -Exchanged immediately upon TCP connection to verify compatibility. - -std::string m_machineId: The name/ID of the machine. - -unsigned m_laneId: The specific lane (usually 0 or 1). - -std::string m_version: Protocol version (Defaults to "1.5"). - -SupportedFeatures m_supportedFeatures: Flags for advanced features. - -2. Board Handover Messages -BoardAvailableData (Sent by Upstream) -Announces a board is waiting to be sent. Contains physical properties. -Required Fields: - -std::string m_boardId: Unique UUID for the board. - -std::string m_boardIdCreatedBy: Machine ID that generated the UUID. - -EBoardQuality m_failedBoard: eANY, eGOOD, or eBAD. - -EFlippedBoard m_flippedBoard: eSIDE_UP_IS_UNKNOWN, eTOP_SIDE_IS_UP, eBOTTOM_SIDE_IS_UP. - -Common Optional Fields: - -Optional m_optionalTopBarcode / m_optionalBottomBarcode - -Optional m_optionalLengthInMM / m_optionalWidthInMM / m_optionalThicknessInMM - -Optional m_optionalWorkOrderId / m_optionalBatchId - -MachineReadyData (Sent by Downstream) -Announces the receiver is empty and ready. - -EBoardQuality m_failedBoard: Specifies what quality the machine accepts (e.g., eGOOD). - -Note: This struct also contains the same optional physical dimensions as BoardAvailableData. Downstream machines can populate these to demand specific board sizes. - -3. Transport Control Messages -StartTransportData (Sent by Downstream) -Commands the Upstream machine to begin pushing the board. - -std::string m_boardId: The UUID of the board being requested. - -Optional m_optionalConveyorSpeedInMMPerSecs: The speed the sender should match. - -TransportFinishedData (Sent by Upstream) -Confirms the board has entirely left the sender's conveyor. - -ETransferState m_transferState: Usually ETransferState::eCOMPLETE. - -std::string m_boardId: The UUID of the transferred board. - -StopTransportData (Sent by Downstream) -Confirms the board has securely arrived inside the receiver. - -ETransferState m_transferState: Usually ETransferState::eCOMPLETE. - -std::string m_boardId: The UUID of the received board. \ No newline at end of file diff --git a/docs/07_Network_and_Topology.md b/docs/07_Network_and_Topology.md deleted file mode 100644 index dcc16e5..0000000 --- a/docs/07_Network_and_Topology.md +++ /dev/null @@ -1,67 +0,0 @@ -# 07. Network Configuration & Topology - -Hermes operates over standard TCP/IP Ethernet. Because it uses standard networking rather than proprietary cables, machines can be wired together in different ways depending on the factory's infrastructure. - -This document explains the physical setups and how to configure your `UpstreamSettings` and `DownstreamSettings` structs to match them. - ---- - -## 1. Physical Topologies - -### Point-to-Point (Direct Connection) -This is the simplest setup and the direct equivalent of legacy SMEMA. Two adjacent machines are connected directly to each other using a standard Ethernet cable. -* **Pros:** Extremely secure; no interference from other factory traffic. -* **Cons:** Requires the machine to have multiple network interface cards (NICs) if it also needs to talk to the factory network (MES). - -### Switched Fabric (Smart Factory) -All machines in the line plug into a central factory Ethernet switch. -* **Pros:** A single physical cable handles both horizontal (machine-to-machine) and vertical (machine-to-MES) data. -* **Cons:** Requires strict IP filtering to ensure Machine A doesn't accidentally send a board to Machine C. - ---- - -## 2. The Hermes Port Standard - -By definition (IPC-HERMES-9852), the TCP port used for a connection is derived from the Lane ID. -* The Base Port is always **50100**. -* Port = Base Port + Lane ID. -* **Therefore, a standard single-lane machine ALWAYS communicates on Port 50101.** - ---- - -## 3. Configuring the Receiver (`DownstreamSettings`) - -The Receiver acts as a TCP Server. It opens a port and waits. - -```cpp -Hermes::DownstreamSettings settings; -settings.m_machineId = "Oven_01"; -settings.m_port = 50101; - -// OPTIONAL BUT RECOMMENDED IN SWITCHED NETWORKS: -// If you leave this blank, ANY machine on the factory floor can connect to your receiver. -// By setting this, you force the socket to ONLY accept connections from the specific -// IP address of the machine physically positioned before you in the line. -settings.m_optionalClientAddress = "192.168.1.50"; - -// Advanced keep-alive timings (defaults are usually fine) -settings.m_checkAlivePeriodInSeconds = 60.0; -settings.m_reconnectWaitTimeInSeconds = 10.0; -4. Configuring the Sender (UpstreamSettings) -The Sender acts as a TCP Client. It actively reaches out across the network to find the next machine. - -C++ -Hermes::UpstreamSettings settings; -settings.m_machineId = "Printer_01"; -settings.m_port = 50101; - -// REQUIRED: -// You must explicitly tell the sender the IP address of the Downstream -// receiver it is supposed to push boards into. -settings.m_hostAddress = "192.168.1.51"; - -// Advanced keep-alive timings -settings.m_checkAlivePeriodInSeconds = 60.0; -settings.m_reconnectWaitTimeInSeconds = 10.0; -5. Network Resilience -The library handles network drops automatically. If a cable is unplugged, the OnDisconnected callback will fire. The Upstream node will then automatically attempt to reconnect to the m_hostAddress every m_reconnectWaitTimeInSeconds (default: 10 seconds) until the connection is restored. \ No newline at end of file diff --git a/docs/08_Legacy_Interface_API.md b/docs/08_Legacy_Interface_API.md deleted file mode 100644 index 00306d7..0000000 --- a/docs/08_Legacy_Interface_API.md +++ /dev/null @@ -1,93 +0,0 @@ -# 08. Legacy Interface API (Advanced) - -While the `HermesModern.hpp` wrapper simplifies development using lambdas, advanced users may prefer to interact directly with the raw C++ library to minimize overhead or to integrate Hermes deeply into an existing class hierarchy. - -To use the raw API, you must inherit from the core interfaces and manage the blocking event loops manually. - ---- - -## 1. The Core Interfaces - -The library provides two primary abstract classes: -* `Hermes::IDownstreamCallback` -* `Hermes::IUpstreamCallback` - -If you inherit directly from these, your compiler will force you to implement every pure virtual method (e.g., `OnConnected`, `OnState`, and the various overloaded `On` methods for data). - ---- - -## 2. The Naming Collision Problem - -In a real factory, most machines sit in the middle of the line. This means your application class will likely need to act as both a Sender and a Receiver. - -If you try to inherit from both base interfaces simultaneously, you will encounter **ambiguous function names**. For example, both interfaces define this exact method: -`virtual void On(unsigned sessionId, EState state, const ServiceDescriptionData& data) = 0;` - -The compiler won't know if you are receiving a service description from the previous machine or the next machine. - ---- - -## 3. The "Helper" Solution - -To solve the naming collision, the library provides two specialized helper structs in `Hermes.hpp`: -* `Hermes::UpstreamCallbackHelper` -* `Hermes::DownstreamCallbackHelper` - -These helpers internally catch the ambiguous `On()` methods and reroute them to uniquely named functions like `OnUpstream(...)` and `OnDownstream(...)`. - ---- - -## 4. Legacy Implementation Example - -Here is how you use the helpers to build a machine that sends and receives simultaneously using the raw API: - -```cpp -#include "Hermes.hpp" -#include -#include - -// 1. Inherit from both Helpers, NOT the base interfaces -class MySmtMachine : public Hermes::UpstreamCallbackHelper, - public Hermes::DownstreamCallbackHelper -{ -public: - // 2. Instantiate the core objects, passing `*this` as the callback reference - MySmtMachine() : m_upstream(1, *this), m_downstream(1, *this) {} - - void Start() { - // 3. YOU must manage the blocking threads manually - m_upThread = std::thread([this]() { m_upstream.Run(); }); - m_downThread = std::thread([this]() { m_downstream.Run(); }); - - Hermes::UpstreamSettings upSettings("MyMachine", "192.168.1.51", 50101); - Hermes::DownstreamSettings downSettings("MyMachine", 50101); - - m_upstream.Enable(upSettings); - m_downstream.Enable(downSettings); - } - - void Stop() { - m_upstream.Stop(); - m_downstream.Stop(); - if (m_upThread.joinable()) m_upThread.join(); - if (m_downThread.joinable()) m_downThread.join(); - } - -protected: - // 4. Implement the uniquely named virtual methods - void OnUpstreamConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { - std::cout << "Sender Connected to next machine!\n"; - } - - void OnDownstreamConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { - std::cout << "Receiver Accepted connection from previous machine!\n"; - } - - // ... You must implement all remaining OnUpstream and OnDownstream virtuals ... - -private: - Hermes::Upstream m_upstream; - Hermes::Downstream m_downstream; - std::thread m_upThread; - std::thread m_downThread; -}; \ No newline at end of file diff --git a/docs/09_Vertical_MES_Integration.md b/docs/09_Vertical_MES_Integration.md deleted file mode 100644 index 4788afb..0000000 --- a/docs/09_Vertical_MES_Integration.md +++ /dev/null @@ -1,86 +0,0 @@ -# 09. Vertical Integration (MES/ERP) - -While `Upstream` and `Downstream` handle **Horizontal** communication (machine passing boards to machine), the Hermes Standard also defines a **Vertical** channel. This allows higher-level factory software (like an MES, ERP, or line controller) to monitor and control the machines in real-time. - -In the ASM `lib_cpp` library, this is handled by the `VerticalService` and `VerticalClient` classes. - ---- - -## 1. The Architecture - -In a standard smart factory setup: -* **The Machine (You):** Acts as the **`VerticalService`** (TCP Server). You open a port (often `1248` or `50100`) and wait. -* **The MES/Factory Cloud:** Acts as the **`VerticalClient`** (TCP Client). It connects to every machine on the line to gather data. - ---- - -## 2. The Vertical Handshake - -When an MES connects to your machine, you must exchange supervisory descriptions. This is entirely separate from the machine-to-machine horizontal handshake. - -1. **MES Connects:** Your `OnConnected` callback fires. -2. **Exchange Descriptions:** The MES sends its `SupervisoryServiceDescriptionData`. Your machine responds with its own, detailing what features it supports (e.g., `FeatureBoardTracking`, `FeatureQueryWorkOrderInfo`). - ---- - -## 3. Real-Time Board Tracking - -The most common use case for Vertical Integration is letting the factory know exactly where every PCB is located. If the MES enables `FeatureBoardTracking`, your machine is responsible for firing off two specific messages during its operation: - -### A. `BoardArrivedData` -You must signal this to the `VerticalService` the moment a board successfully enters your machine. -**Key Fields:** -* `m_machineId`: Your machine's ID. -* `m_boardId`: The UUID of the board that just entered. -* `m_boardTransfer`: How it got there (e.g., `EBoardArrivedTransfer::eTRANSFERRED` from an upstream machine, or `eLOADED` if an operator manually placed it). - -### B. `BoardDepartedData` -You must signal this the moment a board completely leaves your machine. -**Key Fields:** -* `m_machineId`: Your machine's ID. -* `m_boardId`: The UUID of the board. -* `m_boardTransfer`: How it left (e.g., `EBoardDepartedTransfer::eTRANSFERRED` to a downstream machine, or `eREMOVED` if an operator took it out). - ---- - -## 4. Advanced: Work Orders and Routing - -If your machine supports dynamic recipes or routing, the Vertical channel allows you to ask the MES what to do with a board. - -1. **Query:** When a board arrives, your machine sends a `QueryWorkOrderInfoData` containing the board's barcode. -2. **Reply:** The MES responds with `ReplyWorkOrderInfoData`, telling your machine which recipe/program to load for that specific barcode. -3. **Route:** The MES can also inject `m_route` attributes, telling a sorting conveyor whether to pass the board through, send it to an inspection queue, or reject it to a scrap bin. - ---- - -## 5. Raw Implementation Example - -If you are using the raw C++ interface (bypassing lambdas), your vertical implementation will look like this: - -```cpp -#include "Hermes.hpp" - -class MyLineMonitor : public Hermes::IVerticalServiceCallback { -public: - MyLineMonitor() : m_verticalService(*this) {} - - void Start() { - Hermes::VerticalServiceSettings settings("My_Machine", 1248); - m_verticalService.Enable(settings); - // Ensure you run m_verticalService.Run() in a background thread! - } - - // --- Override the pure virtuals --- - void OnConnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::ConnectionInfo& info) override { - // MES has connected to us - } - - void On(unsigned sessionId, Hermes::EVerticalState state, const Hermes::SupervisoryServiceDescriptionData& data) override { - // MES told us who it is. We can now reply with our capabilities. - } - - // ... Implement remaining virtuals ... - -private: - Hermes::VerticalService m_verticalService; -}; \ No newline at end of file diff --git a/docs/BUILDING.md b/docs/BUILDING.md deleted file mode 100644 index 36d59b1..0000000 --- a/docs/BUILDING.md +++ /dev/null @@ -1,100 +0,0 @@ -# Building the Hermes C++ Library - -The Hermes library uses **CMake** as its universal build system. The library has been modernized to use **Header-Only Boost** for its core networking, making it incredibly lightweight and easy to compile natively on Windows, Linux, and macOS without complex binary linking. - ---- - -## 1. Prerequisites - -Before building, ensure your environment has the following installed: - -* **C++17 Compiler**: - * **Linux**: GCC 7+ or Clang 5+ - * **Windows**: MSYS2/MinGW-w64 (GCC) or MSVC (Visual Studio 2017+) - * **macOS**: Apple Clang -* **CMake**: Version 3.15 or higher. -* **Boost Libraries**: Version 1.66 or newer. *(Note: The core library only requires Boost headers. No compiled `.lib` or `.so` Boost binaries are required unless you are building the Test Suite).* -* **pugixml**: Included locally in the `References/` directory, so no external installation is required. - ---- - -## 2. Environment Setup (From Scratch) - -Depending on your operating system, follow the steps below to ensure you have all required build tools present before running CMake. - -### 🐧 Linux (Ubuntu / Debian) -Linux is the easiest platform to build on. Simply install the standard build essentials and Boost headers from your package manager: -```bash -sudo apt update && sudo apt upgrade -y -sudo apt install -y g++ cmake libboost-all-dev git -🪟 Windows (via MSYS2 / MinGW - Recommended) -To build a lightweight .dll using GCC on Windows, use MSYS2 (UCRT64 environment). Open your MSYS2 terminal and install the toolchain: - -Bash -pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-make mingw-w64-ucrt-x86_64-boost -Crucial: If you build from a standard Windows Command Prompt (cmd) instead of the MSYS2 terminal, you must temporarily add the MSYS2 binaries to your path before running CMake: - -DOS -set PATH=C:\msys64\ucrt64\bin;%PATH% -🪟 Windows (via Visual Studio / MSVC) -If you prefer Microsoft Visual Studio, ensure the "Desktop development with C++" workload is installed. You will also need to download Boost headers (e.g., from boost.org) and extract them to a local directory (e.g., C:\local\boost_1_84_0). - -3. Standard Build Process -The CMake workflow is generally identical regardless of your operating system. Open a terminal in the root of the cloned repository. - -Step 1: Create a Build Directory -Always perform an "out-of-source" build to keep your source tree clean. - -Bash -mkdir build -cd build -Step 2: Configure the Project -Run CMake to generate the build files. - -For Linux / macOS: - -Bash -cmake .. -make -j4 -For Windows (MSYS2 / MinGW): - -DOS -cmake .. -G "MinGW Makefiles" -For Windows (Visual Studio): - -DOS -cmake .. -DBOOST_ROOT="C:/local/boost_1_84_0" -Step 3: Compile the Library -Execute the build command to compile the library. - -Bash -cmake --build . --config Release -Build Outputs -Once the build completes successfully, the compiled binaries will be located in the build/src/Hermes/ directory: - -Windows: hermes.dll - -Linux: libhermes.so - -macOS: libhermes.dylib - -4. Building and Running the Tests -The repository includes a test suite (BoostTestHermes) to verify protocol compliance. - -Note: While the core library is header-only, building the tests requires the compiled boost_unit_test_framework binary. - -To run the tests from inside your build directory: - -Bash -ctest --output-on-failure -C Release -Alternatively, you can run the executable directly: - -Windows: .\test\BoostTestHermes\Release\BoostTestHermes.exe - -Linux: ./test/BoostTestHermes/BoostTestHermes - -To compile any file run -g++ -std=c++17 -o example.exe example.cpp \ - -I~/lib_cpp/src/include \ - -L~/lib_cpp/build -lhermes \ - -lboost_system -lpthread \ No newline at end of file diff --git a/examples/interactive_sender.cpp b/examples/interactive_sender.cpp deleted file mode 100644 index 5e48413..0000000 --- a/examples/interactive_sender.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include -#include -#include "HermesModern.hpp" - -void PrintMenu() { - std::cout << "\n=========================================\n"; - std::cout << " INTERACTIVE HERMES SENDER CONSOLE\n"; - std::cout << "=========================================\n"; - std::cout << " Type a command and press Enter:\n"; - std::cout << " service -> Send ServiceDescription\n"; - std::cout << " board -> Send BoardAvailable (e.g., board PCB-123)\n"; - std::cout << " start -> Send TransportFinished (e.g., start PCB-123)\n"; - std::cout << " exit -> Shut down and close\n"; - std::cout << "=========================================\n"; -} - -int main() { - // Senders face DOWNSTREAM and act as TCP Servers - Hermes::Modern::Downstream sender(1); - - sender.RegisterConnectedCallback([&](const Hermes::ConnectionInfo& info) { - std::cout << "\n[NETWORK] Connected to Receiver at " << info.m_address << "!\n> "; - }); - - sender.RegisterMachineReadyCallback([&](const Hermes::MachineReadyData& data) { - std::cout << "\n[RECEIVER SAYS] I am MachineReady! Send me a board.\n> "; - }); - - sender.RegisterStartTransportCallback([&](const Hermes::StartTransportData& data) { - std::cout << "\n[RECEIVER SAYS] StartTransport for " << data.m_boardId << "! My conveyors are running.\n> "; - }); - - sender.RegisterStopTransportCallback([&](const Hermes::StopTransportData& data) { - std::cout << "\n[RECEIVER SAYS] StopTransport for " << data.m_boardId << ". I have the board completely.\n> "; - }); - - std::cout << "Opening Server on port 50101...\n"; - sender.Enable(Hermes::DownstreamSettings("Interactive_Sender", 50101)); - - PrintMenu(); - std::string command; - - while (true) { - std::cout << "> "; - std::cin >> command; - - if (command == "exit") { - break; - } - else if (command == "service") { - sender.Signal(1, Hermes::ServiceDescriptionData("Interactive_Sender", 1)); - std::cout << "[SENT] ServiceDescriptionData\n"; - } - else if (command == "board") { - std::string boardId; - std::cin >> boardId; - sender.Signal(1, Hermes::BoardAvailableData(boardId, "Interactive_Sender", Hermes::EBoardQuality::eGOOD, Hermes::EFlippedBoard::eTOP_SIDE_IS_UP)); - std::cout << "[SENT] BoardAvailableData for " << boardId << "\n"; - } - else if (command == "start") { - std::string boardId; - std::cin >> boardId; - sender.Signal(1, Hermes::TransportFinishedData(Hermes::ETransferState::eCOMPLETE, boardId)); - std::cout << "[SENT] TransportFinishedData for " << boardId << "\n"; - } - else { - std::cout << "[ERROR] Unknown command.\n"; - std::cin.clear(); - std::cin.ignore(10000, '\n'); - } - } - - sender.Stop(); - return 0; -} diff --git a/examples/machine_b_pnp.cpp b/examples/machine_b_pnp.cpp deleted file mode 100644 index 995c848..0000000 --- a/examples/machine_b_pnp.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include -#include -#include -#include -#include "HermesModern.hpp" - -int main() { - std::cout << "========================================\n"; - std::cout << " [MACHINE B] - PICK & PLACE\n"; - std::cout << " Connecting to 50101 (A) | Listening on 50102 (C)\n"; - std::cout << "========================================\n\n"; - - // Receiver connects UPSTREAM to A. Sender listens DOWNSTREAM for C. - Hermes::Modern::Upstream receiver(1); - Hermes::Modern::Downstream sender(1); - - // --- RECEIVING FROM MACHINE A (Upstream) --- - receiver.RegisterServiceDescriptionCallback([&](const Hermes::ServiceDescriptionData& data) { - std::cout << "[B] Upstream " << data.m_machineId << " connected. I am ready.\n"; - receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); - }); - - receiver.RegisterBoardAvailableCallback([&](const Hermes::BoardAvailableData& board) { - std::cout << "[B] Board " << board.m_boardId << " waiting at upstream edge. Pulling...\n"; - receiver.Signal(1, Hermes::StartTransportData(board.m_boardId)); - }); - - receiver.RegisterTransportFinishedCallback([&](const Hermes::TransportFinishedData& data) { - receiver.Signal(1, Hermes::StopTransportData(Hermes::ETransferState::eCOMPLETE, data.m_boardId)); - std::cout << "[B] Board " << data.m_boardId << " received. Placing components...\n"; - - std::this_thread::sleep_for(std::chrono::seconds(3)); - std::cout << "[B] Placement done! Passing to Machine C...\n"; - - sender.Signal(1, Hermes::BoardAvailableData(data.m_boardId, "Machine_B_PnP", Hermes::EBoardQuality::eGOOD, Hermes::EFlippedBoard::eTOP_SIDE_IS_UP)); - }); - - // --- SENDING TO MACHINE C (Downstream) --- - sender.RegisterConnectedCallback([&](const Hermes::ConnectionInfo& info) { - std::cout << "[B] Connected to Downstream at " << info.m_address << "\n"; - sender.Signal(1, Hermes::ServiceDescriptionData("Machine_B_PnP", 1)); - }); - - sender.RegisterMachineReadyCallback([&](const Hermes::MachineReadyData&) { - std::cout << "[B] Machine C is ready for boards.\n"; - }); - - sender.RegisterStartTransportCallback([&](const Hermes::StartTransportData& data) { - std::cout << "[B] Machine C is pulling. Running exit conveyors...\n"; - std::this_thread::sleep_for(std::chrono::seconds(1)); - - sender.Signal(1, Hermes::TransportFinishedData(Hermes::ETransferState::eCOMPLETE, data.m_boardId)); - std::cout << "[B] Board successfully handed off to Machine C.\n\n"; - - receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); - }); - - // Start Server first, then connect Client - sender.Enable(Hermes::DownstreamSettings("Machine_B_PnP", 50102)); - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - receiver.Enable(Hermes::UpstreamSettings("Machine_B_PnP", "127.0.0.1", 50101)); - - std::cout << "Press Enter to shut down Machine B...\n\n"; - std::cin.get(); - receiver.Stop(); - sender.Stop(); - return 0; -} \ No newline at end of file diff --git a/examples/machine_c_oven.cpp b/examples/machine_c_oven.cpp deleted file mode 100644 index 8dba590..0000000 --- a/examples/machine_c_oven.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include -#include "HermesModern.hpp" - -int main() { - std::cout << "========================================\n"; - std::cout << " [MACHINE C] - REFLOW OVEN (Receiver)\n"; - std::cout << " Connecting to Port 50102...\n"; - std::cout << "========================================\n\n"; - - // Receivers face UPSTREAM to connect to the previous machine - Hermes::Modern::Upstream receiver(1); - - receiver.RegisterServiceDescriptionCallback([&](const Hermes::ServiceDescriptionData& data) { - std::cout << "[C] Connected to " << data.m_machineId << ". Ready for boards.\n"; - receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); - }); - - receiver.RegisterBoardAvailableCallback([&](const Hermes::BoardAvailableData& board) { - std::cout << "[C] Board detected at entrance: " << board.m_boardId << "\n"; - std::cout << "[C] Starting conveyors to pull it in...\n"; - receiver.Signal(1, Hermes::StartTransportData(board.m_boardId)); - }); - - receiver.RegisterTransportFinishedCallback([&](const Hermes::TransportFinishedData& data) { - receiver.Signal(1, Hermes::StopTransportData(Hermes::ETransferState::eCOMPLETE, data.m_boardId)); - std::cout << "[C] Board " << data.m_boardId << " is fully inside!\n"; - std::cout << "[C] Baking... Handover cycle complete.\n\n"; - - receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); - }); - - // Client connects to the Sender - Hermes::UpstreamSettings settings("Machine_C_Oven", "127.0.0.1", 50102); - receiver.Enable(settings); - - std::cout << "Press Enter to shut down Machine C...\n\n"; - std::cin.get(); - receiver.Stop(); - return 0; -} \ No newline at end of file diff --git a/examples/rpi1_upstream.cpp b/examples/rpi1_upstream.cpp index 960514d..9af0473 100644 --- a/examples/rpi1_upstream.cpp +++ b/examples/rpi1_upstream.cpp @@ -1,155 +1,93 @@ -// ============================================================================= -// rpi1_upstream.cpp — RPi1: Upstream machine -// -// Build: -// g++ -std=c++17 -o rpi1_upstream rpi1_upstream.cpp \ -// -I/path/to/hermes/src/include \ -// -L/path/to/hermes/build -lhermes \ -// -lboost_system -lpthread -// -// Run: -// LD_LIBRARY_PATH=/path/to/hermes/build ./rpi1_upstream -// -// What it does: -// - Connects to RPi2 (Downstream) on port 50100 -// - Sends ServiceDescription to identify itself -// - Waits for ServiceDescription back from RPi2 -// - Prints connection state changes to console -// ============================================================================= - -#include "Hermes.hpp" + +#include "HermesModern.hpp" #include -#include #include #include #include #include -// ----------------------------------------------------------------------- -// EDIT THIS: set RPi2's IP address here -// On RPi2, run: hostname -I -// ----------------------------------------------------------------------- -static const std::string RPI2_IP = "192.168.1.102"; // <-- CHANGE THIS -static const uint16_t HERMES_PORT = 50100; -static const std::string THIS_MACHINE_ID = "RPi1-Upstream"; - static std::atomic g_running{true}; -void signalHandler(int) { g_running = false; } - -// ----------------------------------------------------------------------- -// Upstream callback — receives messages FROM the Downstream (RPi2) -// ----------------------------------------------------------------------- -struct UpstreamCallback : Hermes::IUpstreamCallback -{ - void OnConnected(unsigned sessionId, Hermes::EState state, - const Hermes::ConnectionInfo& info) override - { - std::cout << "[RPi1] Connected to RPi2" - << " address=" << info.m_address - << " port=" << info.m_port - << " session=" << sessionId - << "\n"; - } - - // RPi2 sends its ServiceDescription — we receive it here - void On(unsigned sessionId, Hermes::EState state, - const Hermes::ServiceDescriptionData& data) override - { - std::cout << "[RPi1] Received ServiceDescription from RPi2" - << " machineId=" << data.m_machineId - << " laneId=" << data.m_laneId - << "\n"; - std::cout << "[RPi1] ** Hermes connection established successfully **\n"; - } - - // These are required by the interface but not relevant for this demo - void On(unsigned, Hermes::EState, const Hermes::BoardAvailableData&) override {} - void On(unsigned, Hermes::EState, const Hermes::RevokeBoardAvailableData&) override {} - void On(unsigned, Hermes::EState, const Hermes::TransportFinishedData&) override {} - - void On(unsigned sessionId, const Hermes::NotificationData& data) override - { - std::cout << "[RPi1] Notification from RPi2: " << data.m_description << "\n"; - } - - void On(unsigned, const Hermes::CheckAliveData&) override {} - - void On(unsigned sessionId, const Hermes::CommandData&) override {} - - void OnState(unsigned sessionId, Hermes::EState state) override - { - std::cout << "[RPi1] State changed: " << static_cast(state) << "\n"; - } - - void OnDisconnected(unsigned sessionId, Hermes::EState state, - const Hermes::Error& error) override - { - std::cout << "[RPi1] Disconnected from RPi2"; - if (error) - std::cout << " reason=" << error.m_text; - std::cout << "\n"; - } - - void OnTrace(unsigned, Hermes::ETraceType type, Hermes::StringView trace) override - { - // Uncomment to see full protocol trace: - // std::cout << "[RPi1][TRACE] " << std::string(trace) << "\n"; - } -}; - int main() { - std::signal(SIGINT, signalHandler); - std::signal(SIGTERM, signalHandler); + std::signal(SIGINT, [](int) { g_running = false; }); - std::cout << "[RPi1] Starting Hermes Upstream\n"; - std::cout << "[RPi1] Connecting to RPi2 at " << RPI2_IP - << ":" << HERMES_PORT << "\n"; + Hermes::Modern::Upstream us(1); // lane 1 - UpstreamCallback callback; - Hermes::Upstream upstream(1, callback); // lane 1 + us.RegisterConnectedCallback([](unsigned sessionId, const Hermes::ConnectionInfo& info) { + std::cout << "[US] Connected to downstream at " << info.m_address << "\n"; + }); - // Settings: who we are, and who we connect to - Hermes::UpstreamSettings settings; - settings.m_machineId = THIS_MACHINE_ID; - settings.m_hostAddress = RPI2_IP; - settings.m_port = HERMES_PORT; - settings.m_checkAlivePeriodInSeconds = 60.0; - settings.m_reconnectWaitTimeInSeconds = 5.0; - - // Enable runs the TCP connection + Hermes handshake in background - upstream.Enable(settings); - - // Run the Hermes event loop in a thread - std::thread networkThread([&upstream]() { - upstream.Run(); + us.RegisterDisconnectedCallback([](unsigned sessionId, const Hermes::Error& err) { + std::cout << "[US] Disconnected\n"; }); - // After connection, send our ServiceDescription to RPi2 - // We post it onto the Hermes thread after a short delay to let - // the connection establish first - std::this_thread::sleep_for(std::chrono::seconds(2)); - - upstream.Post([&upstream]() { - Hermes::ServiceDescriptionData desc; - desc.m_machineId = THIS_MACHINE_ID; - desc.m_laneId = 1; - // session 0 = send to whatever session is currently active - // The library fills in the real session id internally via Post - std::cout << "[RPi1] Sending ServiceDescription to RPi2\n"; + us.RegisterStateChangeCallback([](unsigned sessionId, Hermes::EState state) { + std::cout << "[US] State: " << state << "\n"; }); - std::cout << "[RPi1] Running. Press Ctrl+C to stop.\n"; + // --- Receive ServiceDescription, reply and send MachineReady --- + us.RegisterServiceDescriptionCallback( + [&us](unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) { + std::cout << "[US] Received ServiceDescription from: " << data.m_machineId << "\n"; + + us.Post([&us, sessionId]() { + // Send our ServiceDescription + Hermes::ServiceDescriptionData reply; + reply.m_machineId = "Machine-Upstream"; + reply.m_laneId = 1; + us.Signal(sessionId, reply); + + // Then send MachineReady + Hermes::MachineReadyData ready; + ready.m_failedBoard = Hermes::EBoardQuality::eANY; + us.Signal(sessionId, ready); + std::cout << "[US] Sent ServiceDescription + MachineReady\n"; + }); + }); + + // --- Receive BoardAvailable, trigger StartTransport --- + us.RegisterBoardAvailableCallback( + [&us](unsigned sessionId, Hermes::EState, const Hermes::BoardAvailableData& board) { + std::cout << "[US] BoardAvailable: " << board.m_boardId << "\n"; + + us.Post([&us, sessionId, boardId = board.m_boardId]() { + Hermes::StartTransportData start; + start.m_boardId = boardId; + us.Signal(sessionId, start); + std::cout << "[US] StartTransport sent\n"; + }); + }); + + // --- Receive TransportFinished, send StopTransport --- + us.RegisterTransportFinishedCallback( + [&us](unsigned sessionId, Hermes::EState, const Hermes::TransportFinishedData& data) { + std::cout << "[US] TransportFinished for board: " << data.m_boardId << "\n"; + + us.Post([&us, sessionId, boardId = data.m_boardId]() { + Hermes::StopTransportData stop; + stop.m_transferState = Hermes::ETransferState::eCOMPLETE; + stop.m_boardId = boardId; + us.Signal(sessionId, stop); + std::cout << "[US] StopTransport sent — full board transfer complete\n"; + }); + }); + + us.RegisterNotificationCallback([](unsigned, const Hermes::NotificationData& n) { + std::cout << "[US] Notification: " << n.m_description << "\n"; + }); - while (g_running) - std::this_thread::sleep_for(std::chrono::milliseconds(200)); + // EDIT THIS — set the downstream machine's IP: + Hermes::UpstreamSettings settings; + settings.m_machineId = "Machine-Upstream"; + settings.m_hostAddress = "192.168.1.2"; // <-- downstream machine IP + settings.m_port = 50100; + us.Enable(settings); - std::cout << "[RPi1] Shutting down...\n"; - upstream.Stop(); - if (networkThread.joinable()) - networkThread.join(); + std::cout << "[US] Connecting to 192.168.1.2:50100. Ctrl+C to stop.\n"; + while (g_running) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); - std::cout << "[RPi1] Done.\n"; + us.Stop(); return 0; -} +} \ No newline at end of file diff --git a/examples/rpi2_downstream.cpp b/examples/rpi2_downstream.cpp index 0547f36..8f6f69f 100644 --- a/examples/rpi2_downstream.cpp +++ b/examples/rpi2_downstream.cpp @@ -1,157 +1,94 @@ -// ============================================================================= -// rpi2_downstream.cpp — RPi2: Downstream machine -// -// Build: -// g++ -std=c++17 -o rpi2_downstream rpi2_downstream.cpp \ -// -I/path/to/hermes/src/include \ -// -L/path/to/hermes/build -lhermes \ -// -lboost_system -lpthread -// -// Run: -// LD_LIBRARY_PATH=/path/to/hermes/build ./rpi2_downstream -// -// What it does: -// - Listens on port 50100 for an incoming connection from RPi1 -// - Receives ServiceDescription from RPi1 -// - Sends ServiceDescription back to RPi1 -// - Prints connection state changes to console -// ============================================================================= - -#include "Hermes.hpp" + +#include "HermesModern.hpp" #include -#include #include #include #include #include -static const uint16_t HERMES_PORT = 50100; -static const std::string THIS_MACHINE_ID = "RPi2-Downstream"; - -static std::atomic g_running{true}; -static unsigned g_activeSession{0}; - -// Forward declare so callback can reference it -Hermes::Downstream* g_downstream = nullptr; +static std::atomic g_running{true}; -void signalHandler(int) { g_running = false; } - -// ----------------------------------------------------------------------- -// Downstream callback — receives messages FROM the Upstream (RPi1) -// ----------------------------------------------------------------------- -struct DownstreamCallback : Hermes::IDownstreamCallback +int main() { - void OnConnected(unsigned sessionId, Hermes::EState state, - const Hermes::ConnectionInfo& info) override - { - g_activeSession = sessionId; - std::cout << "[RPi2] RPi1 connected" - << " address=" << info.m_address - << " port=" << info.m_port - << " session=" << sessionId - << "\n"; - } - - // RPi1 sends its ServiceDescription — we receive it here - void On(unsigned sessionId, Hermes::EState state, - const Hermes::ServiceDescriptionData& data) override - { - std::cout << "[RPi2] Received ServiceDescription from RPi1" - << " machineId=" << data.m_machineId - << " laneId=" << data.m_laneId - << "\n"; - - // Respond with our own ServiceDescription - if (g_downstream) - { - g_downstream->Post([sessionId]() { - Hermes::ServiceDescriptionData reply; - reply.m_machineId = THIS_MACHINE_ID; - reply.m_laneId = 1; + std::signal(SIGINT, [](int) { g_running = false; }); - std::cout << "[RPi2] Sending ServiceDescription back to RPi1\n"; - g_downstream->Signal(sessionId, reply); - std::cout << "[RPi2] ** Hermes connection established successfully **\n"; - }); - } - } - - // These are required by the interface — not used in this demo - void On(unsigned, Hermes::EState, const Hermes::MachineReadyData&) override {} - void On(unsigned, Hermes::EState, const Hermes::RevokeMachineReadyData&) override {} - void On(unsigned, Hermes::EState, const Hermes::StartTransportData&) override {} - void On(unsigned, Hermes::EState, const Hermes::StopTransportData&) override {} - - void On(unsigned sessionId, const Hermes::NotificationData& data) override - { - std::cout << "[RPi2] Notification from RPi1: " << data.m_description << "\n"; - } - - void On(unsigned, const Hermes::CheckAliveData&) override {} - - void On(unsigned, const Hermes::CommandData&) override {} - - void OnState(unsigned sessionId, Hermes::EState state) override - { - std::cout << "[RPi2] State changed: " << static_cast(state) << "\n"; - } - - void OnDisconnected(unsigned sessionId, Hermes::EState state, - const Hermes::Error& error) override - { - std::cout << "[RPi2] RPi1 disconnected"; - if (error) - std::cout << " reason=" << error.m_text; - std::cout << "\n"; - g_activeSession = 0; - } - - void OnTrace(unsigned, Hermes::ETraceType, Hermes::StringView) override - { - // Uncomment to see full protocol trace: - // std::cout << "[RPi2][TRACE] " << std::string(trace) << "\n"; - } -}; + Hermes::Modern::Downstream ds(1); // lane 1 -int main() -{ - std::signal(SIGINT, signalHandler); - std::signal(SIGTERM, signalHandler); + // --- Connection events --- + ds.RegisterConnectedCallback([](unsigned sessionId, const Hermes::ConnectionInfo& info) { + std::cout << "[DS] Upstream connected from " << info.m_address << "\n"; + }); - std::cout << "[RPi2] Starting Hermes Downstream\n"; - std::cout << "[RPi2] Listening on port " << HERMES_PORT - << " for RPi1...\n"; + ds.RegisterDisconnectedCallback([](unsigned sessionId, const Hermes::Error& err) { + std::cout << "[DS] Upstream disconnected\n"; + }); - DownstreamCallback callback; - Hermes::Downstream downstream(1, callback); // lane 1 - g_downstream = &downstream; + ds.RegisterStateChangeCallback([](unsigned sessionId, Hermes::EState state) { + std::cout << "[DS] State: " << state << "\n"; + }); - // Settings: who we are, and which port to listen on - // m_optionalClientAddress is left empty = accept from any IP - Hermes::DownstreamSettings settings; - settings.m_machineId = THIS_MACHINE_ID; - settings.m_port = HERMES_PORT; - settings.m_checkAlivePeriodInSeconds = 60.0; - settings.m_reconnectWaitTimeInSeconds = 5.0; + // --- Receive ServiceDescription from Upstream, reply with ours --- + ds.RegisterServiceDescriptionCallback( + [&ds](unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) { + std::cout << "[DS] Received ServiceDescription from: " << data.m_machineId << "\n"; - downstream.Enable(settings); + // Reply with our own ServiceDescription + ds.Post([&ds, sessionId]() { + Hermes::ServiceDescriptionData reply; + reply.m_machineId = "Machine-Downstream"; + reply.m_laneId = 1; + ds.Signal(sessionId, reply); + std::cout << "[DS] Sent ServiceDescription\n"; + }); + }); + + // --- When Upstream is MachineReady, send BoardAvailable --- + ds.RegisterMachineReadyCallback( + [&ds](unsigned sessionId, Hermes::EState, const Hermes::MachineReadyData&) { + std::cout << "[DS] Upstream is MachineReady — sending BoardAvailable\n"; + + ds.Post([&ds, sessionId]() { + Hermes::BoardAvailableData board; + board.m_boardId = "PCB-2024-001"; + board.m_boardIdCreatedBy = "Machine-Downstream"; + board.m_failedBoard = Hermes::EBoardQuality::eGOOD; + board.m_flippedBoard = Hermes::EFlippedBoard::eTOP_SIDE_IS_UP; + ds.Signal(sessionId, board); + }); + }); + + // --- When Upstream starts transport, confirm when done --- + ds.RegisterStartTransportCallback( + [&ds](unsigned sessionId, Hermes::EState, const Hermes::StartTransportData& data) { + std::cout << "[DS] StartTransport for board: " << data.m_boardId << "\n"; + + // Simulate board moving + std::this_thread::sleep_for(std::chrono::seconds(1)); + + ds.Post([&ds, sessionId, boardId = data.m_boardId]() { + Hermes::TransportFinishedData finished; + finished.m_transferState = Hermes::ETransferState::eCOMPLETE; + finished.m_boardId = boardId; + ds.Signal(sessionId, finished); + std::cout << "[DS] TransportFinished sent\n"; + }); + }); - // Run Hermes event loop in a background thread - std::thread networkThread([&downstream]() { - downstream.Run(); + // --- Notifications --- + ds.RegisterNotificationCallback([](unsigned, const Hermes::NotificationData& n) { + std::cout << "[DS] Notification: " << n.m_description << "\n"; }); - std::cout << "[RPi2] Running. Press Ctrl+C to stop.\n"; + // --- Start --- + Hermes::DownstreamSettings settings; + settings.m_machineId = "Machine-Downstream"; + settings.m_port = 50100; + ds.Enable(settings); + std::cout << "[DS] Listening on port 50100. Ctrl+C to stop.\n"; while (g_running) - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - - std::cout << "[RPi2] Shutting down...\n"; - downstream.Stop(); - if (networkThread.joinable()) - networkThread.join(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); - g_downstream = nullptr; - std::cout << "[RPi2] Done.\n"; + ds.Stop(); return 0; -} +} \ No newline at end of file diff --git a/src/include/HermesDataConversion.hpp b/src/include/HermesDataConversion.hpp index 114a37c..505a9f1 100644 --- a/src/include/HermesDataConversion.hpp +++ b/src/include/HermesDataConversion.hpp @@ -22,111 +22,145 @@ limitations under the License. namespace Hermes { - // check whether we got the constants right: + // Verify constants match between C and C++ headers static_assert(cCONFIG_PORT == cHERMES_CONFIG_PORT, ""); static_assert(cBASE_PORT == cHERMES_BASE_PORT, ""); static_assert(cMAX_MESSAGE_SIZE == cHERMES_MAX_MESSAGE_SIZE, ""); - inline void CppToC(const std::string& data, HermesStringView& result) { result = {data.data(), data.size()}; } - inline void CToCpp(HermesStringView data, std::string& result) { result.assign(data.m_pData, data.m_size); } - inline HermesStringView ToC(StringView data) { return{data.data(), data.size()}; } - inline StringView ToCpp(HermesStringView data) { return{data.m_pData, data.m_size}; } + // ------------------------------------------------------------------------- + // String conversions + // FIX: HermesStringView now has m_pData and m_size (fixed in HermesStringView.h) + // FIX: std::optional uses value_or and has_value instead of custom Optional + // ------------------------------------------------------------------------- + inline void CppToC(const std::string& data, HermesStringView& result) + { + result.m_pData = data.data(); + result.m_size = data.size(); + } + + inline void CToCpp(HermesStringView data, std::string& result) + { + result.assign(data.m_pData, data.m_size); + } + + inline HermesStringView ToC(StringView data) + { + return { data.data(), data.size() }; + } + + inline StringView ToCpp(HermesStringView data) + { + return { data.m_pData, data.m_size }; + } inline void CppToC(const Optional& data, HermesStringView& result) - { - result = data ? HermesStringView{data->data(), data->size()} : HermesStringView{}; + { + if (data.has_value()) + { + result.m_pData = data->data(); + result.m_size = data->size(); + } + else + { + result.m_pData = nullptr; + result.m_size = 0; + } } + inline void CToCpp(HermesStringView data, Optional& result) { - result = data.m_pData ? Optional{data.m_pData, data.m_size} : Optional{}; + if (data.m_pData) + result = std::string(data.m_pData, data.m_size); + else + result = std::nullopt; } - inline void CppToC(double data, double& result) { result = data; } - inline void CToCpp(double data, double& result) { result = data; } - + // ------------------------------------------------------------------------- + // Scalar pass-throughs + // ------------------------------------------------------------------------- + inline void CppToC(double data, double& result) { result = data; } + inline void CToCpp(double data, double& result) { result = data; } inline void CppToC(unsigned data, unsigned& result) { result = data; } inline void CToCpp(unsigned data, unsigned& result) { result = data; } - inline void CppToC(uint16_t data, uint16_t& result) { result = data; } inline void CToCpp(uint16_t data, uint16_t& result) { result = data; } + inline void CppToC(uint32_t data, uint32_t& result) { result = data; } + inline void CToCpp(uint32_t data, uint32_t& result) { result = data; } + // ------------------------------------------------------------------------- + // Optional pointer conversions + // ------------------------------------------------------------------------- template void CToCpp(const CT* pData, CppT& result) { - if (!pData) - return; - + if (!pData) return; CToCpp(*pData, result); } template void CppToC(const Optional& data, const T*& result) { - result = data ? &*data : nullptr; + result = data.has_value() ? &*data : nullptr; } template void CToCpp(const CT* pData, Optional& result) { - if (!pData) - return; - - result.emplace(); - CToCpp(*pData, *result); + if (!pData) { result = std::nullopt; return; } + CppT value{}; + CToCpp(*pData, value); + result = std::move(value); } template void CppToC(const Optional& data, Optional& result) { - if (!data) - return; - - result.emplace(CT{}); - CppToC(*data, *result); + if (!data.has_value()) { result = std::nullopt; return; } + CT c{}; + CppToC(*data, c); + result = std::move(c); } template void CppToC(const Optional& data, CT& intermediate, const CT*& result) { - if (!data) - return; - + if (!data.has_value()) { result = nullptr; return; } CppToC(*data, intermediate); result = &intermediate; } - // struct for converting vom cpp to c vector: + // ------------------------------------------------------------------------- + // Vector conversions + // ------------------------------------------------------------------------- template struct VectorHolder { - std::vector m_values; + std::vector m_values; std::vector m_pointers; }; template void CppToC(const std::vector& data, VectorHolder& result) { - auto size = data.size(); - result.m_values.resize(size, CT{}); - for (uint32_t i = 0; i < size; ++i) - { + auto sz = data.size(); + result.m_values.resize(sz, CT{}); + for (uint32_t i = 0; i < sz; ++i) CppToC(data[i], result.m_values[i]); - } } template void CppToC(const std::vector& data, VectorHolder& intermediate, CVector& result) { - auto size = data.size(); - intermediate.m_values.resize(size, CT{}); - intermediate.m_pointers.resize(size); - for (uint32_t i = 0; i < size; ++i) + auto sz = data.size(); + intermediate.m_values.resize(sz, CT{}); + intermediate.m_pointers.resize(sz); + for (uint32_t i = 0; i < sz; ++i) { CppToC(data[i], intermediate.m_values[i]); intermediate.m_pointers[i] = &intermediate.m_values[i]; } - result.m_pData = size == 0 ? nullptr : intermediate.m_pointers.data(); - result.m_size = size; + result.m_pData = (sz == 0) ? nullptr : intermediate.m_pointers.data(); + result.m_size = sz; } template @@ -134,159 +168,153 @@ namespace Hermes { result.resize(data.m_size); for (uint32_t i = 0; i < data.m_size; ++i) - { CToCpp(*data.m_pData[i], result[i]); - } } - // enums - static_assert(size(EState()) == cHERMES_STATE_ENUM_SIZE, "enum mismatch"); - inline EHermesState ToC(EState data) { return static_cast(data); } - inline EState ToCpp(EHermesState data) { return static_cast(data); } + // ------------------------------------------------------------------------- + // Enum conversions — static_assert verifies C and C++ enums stay in sync + // ------------------------------------------------------------------------- + static_assert(size(EState()) == cHERMES_STATE_ENUM_SIZE, "enum mismatch"); + inline EHermesState ToC(EState data) { return static_cast(data); } + inline EState ToCpp(EHermesState data) { return static_cast(data); } static_assert(size(ETraceType()) == cHERMES_TRACE_TYPE_ENUM_SIZE, "enum mismatch"); - inline EHermesTraceType ToC(ETraceType data) { return static_cast(data); } - inline ETraceType ToCpp(EHermesTraceType data) { return static_cast(data); } + inline EHermesTraceType ToC(ETraceType data) { return static_cast(data); } + inline ETraceType ToCpp(EHermesTraceType data) { return static_cast(data); } static_assert(size(ECheckAliveType()) == cHERMES_CHECK_ALIVE_TYPE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckAliveType data, EHermesCheckAliveType& result) { result = static_cast(data); } - inline void CToCpp(EHermesCheckAliveType data, ECheckAliveType& result) { result = static_cast(data); } + inline void CppToC(ECheckAliveType data, EHermesCheckAliveType& r) { r = static_cast(data); } + inline void CToCpp(EHermesCheckAliveType data, ECheckAliveType& r) { r = static_cast(data); } - static_assert(size(EBoardQuality()) == cHERMES_BOARD_QUALITY_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardQuality data, EHermesBoardQuality& result) { result = static_cast(data); } - inline void CToCpp(EHermesBoardQuality data, EBoardQuality& result) { result = static_cast(data); } + static_assert(size(EBoardQuality()) == cHERMES_BOARD_QUALITY_ENUM_SIZE, "enum mismatch"); + inline void CppToC(EBoardQuality data, EHermesBoardQuality& r) { r = static_cast(data); } + inline void CToCpp(EHermesBoardQuality data, EBoardQuality& r) { r = static_cast(data); } - static_assert(size(EFlippedBoard()) == cHERMES_FLIPPED_BOARD_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EFlippedBoard data, EHermesFlippedBoard& result) { result = static_cast(data); } - inline void CToCpp(EHermesFlippedBoard data, EFlippedBoard& result) { result = static_cast(data); } + static_assert(size(EFlippedBoard()) == cHERMES_FLIPPED_BOARD_ENUM_SIZE, "enum mismatch"); + inline void CppToC(EFlippedBoard data, EHermesFlippedBoard& r) { r = static_cast(data); } + inline void CToCpp(EHermesFlippedBoard data, EFlippedBoard& r) { r = static_cast(data); } static_assert(size(ESubBoardState()) == cHERMES_SUB_BOARD_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ESubBoardState data, EHermesSubBoardState& result) { result = static_cast(data); } - inline void CToCpp(EHermesSubBoardState data, ESubBoardState& result) { result = static_cast(data); } + inline void CppToC(ESubBoardState data, EHermesSubBoardState& r) { r = static_cast(data); } + inline void CToCpp(EHermesSubBoardState data, ESubBoardState& r) { r = static_cast(data); } - static_assert(size(ETransferState()) == cHERMES_TRANSFER_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ETransferState data, EHermesTransferState& result) { result = static_cast(data); } - inline void CToCpp(EHermesTransferState data, ETransferState& result) { result = static_cast(data); } + static_assert(size(ETransferState()) == cHERMES_TRANSFER_STATE_ENUM_SIZE, "enum mismatch"); + inline void CppToC(ETransferState data, EHermesTransferState& r) { r = static_cast(data); } + inline void CToCpp(EHermesTransferState data, ETransferState& r) { r = static_cast(data); } - static_assert(size(ENotificationCode()) == cHERMES_NOTIFICATION_CODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ENotificationCode data, EHermesNotificationCode& result) { result = static_cast(data); } - inline void CToCpp(EHermesNotificationCode data, ENotificationCode& result) { result = static_cast(data); } + static_assert(size(ENotificationCode()) == cHERMES_NOTIFICATION_CODE_ENUM_SIZE, "enum mismatch"); + inline void CppToC(ENotificationCode data, EHermesNotificationCode& r) { r = static_cast(data); } + inline void CToCpp(EHermesNotificationCode data, ENotificationCode& r) { r = static_cast(data); } - static_assert(size(ESeverity()) == cHERMES_SEVERITY_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ESeverity data, EHermesSeverity& result) { result = static_cast(data); } - inline void CToCpp(EHermesSeverity data, ESeverity& result) { result = static_cast(data); } + static_assert(size(ESeverity()) == cHERMES_SEVERITY_ENUM_SIZE, "enum mismatch"); + inline void CppToC(ESeverity data, EHermesSeverity& r) { r = static_cast(data); } + inline void CToCpp(EHermesSeverity data, ESeverity& r) { r = static_cast(data); } - static_assert(size(ECheckState()) == cHERMES_CHECK_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckState data, EHermesCheckState& result) { result = static_cast(data); } - inline void CToCpp(EHermesCheckState data, ECheckState& result) { result = static_cast(data); } + static_assert(size(ECheckState()) == cHERMES_CHECK_STATE_ENUM_SIZE, "enum mismatch"); + inline void CppToC(ECheckState data, EHermesCheckState& r) { r = static_cast(data); } + inline void CToCpp(EHermesCheckState data, ECheckState& r) { r = static_cast(data); } - static_assert(size(EErrorCode()) == cHERMES_ERROR_CODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EErrorCode data, EHermesErrorCode& result) { result = static_cast(data); } - inline void CToCpp(EHermesErrorCode data, EErrorCode& result) { result = static_cast(data); } + static_assert(size(EErrorCode()) == cHERMES_ERROR_CODE_ENUM_SIZE, "enum mismatch"); + inline void CppToC(EErrorCode data, EHermesErrorCode& r) { r = static_cast(data); } + inline void CToCpp(EHermesErrorCode data, EErrorCode& r) { r = static_cast(data); } static_assert(size(ECheckAliveResponseMode()) == cHERMES_CHECK_ALIVE_RESPONSE_MODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckAliveResponseMode data, EHermesCheckAliveResponseMode& result) { result = static_cast(data); } - inline void CToCpp(EHermesCheckAliveResponseMode data, ECheckAliveResponseMode& result) { result = static_cast(data); } + inline void CppToC(ECheckAliveResponseMode data, EHermesCheckAliveResponseMode& r) { r = static_cast(data); } + inline void CToCpp(EHermesCheckAliveResponseMode data, ECheckAliveResponseMode& r) { r = static_cast(data); } static_assert(size(EBoardArrivedTransfer()) == cHERMES_BOARD_ARRIVED_TRANSFER_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardArrivedTransfer data, EHermesBoardArrivedTransfer& result) { result = static_cast(data); } - inline void CToCpp(EHermesBoardArrivedTransfer data, EBoardArrivedTransfer& result) { result = static_cast(data); } + inline void CppToC(EBoardArrivedTransfer data, EHermesBoardArrivedTransfer& r) { r = static_cast(data); } + inline void CToCpp(EHermesBoardArrivedTransfer data, EBoardArrivedTransfer& r) { r = static_cast(data); } static_assert(size(EBoardDepartedTransfer()) == cHERMES_BOARD_DEPARTED_TRANSFER_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardDepartedTransfer data, EHermesBoardDepartedTransfer& result) { result = static_cast(data); } - inline void CToCpp(EHermesBoardDepartedTransfer data, EBoardDepartedTransfer& result) { result = static_cast(data); } + inline void CppToC(EBoardDepartedTransfer data, EHermesBoardDepartedTransfer& r) { r = static_cast(data); } + inline void CToCpp(EHermesBoardDepartedTransfer data, EBoardDepartedTransfer& r) { r = static_cast(data); } static_assert(size(EReplyWorkOrderInfoStatus()) == cHERMES_REPLY_WORK_ORDER_INFO_STATUS_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EReplyWorkOrderInfoStatus data, EHermesReplyWorkOrderInfoStatus& result) { result = static_cast(data); } - inline void CToCpp(EHermesReplyWorkOrderInfoStatus data, EReplyWorkOrderInfoStatus& result) { result = static_cast(data); } + inline void CppToC(EReplyWorkOrderInfoStatus data, EHermesReplyWorkOrderInfoStatus& r) { r = static_cast(data); } + inline void CToCpp(EHermesReplyWorkOrderInfoStatus data, EReplyWorkOrderInfoStatus& r) { r = static_cast(data); } static_assert(size(EVerticalState()) == cHERMES_VERTICAL_STATE_ENUM_SIZE, "enum mismatch"); - inline EHermesVerticalState ToC(EVerticalState data) { return static_cast(data); } - inline EVerticalState ToCpp(EHermesVerticalState data) { return static_cast(data); } + inline EHermesVerticalState ToC(EVerticalState data) { return static_cast(data); } + inline EVerticalState ToCpp(EHermesVerticalState data) { return static_cast(data); } + // ------------------------------------------------------------------------- + // Forward declarations — specialisations defined below + // ------------------------------------------------------------------------- + template struct Converter2C; + template struct ConverterBase + { + ConverterBase() = default; + ConverterBase(const ConverterBase&) = delete; + ConverterBase(ConverterBase&&) = delete; + ConverterBase& operator=(const ConverterBase&) = delete; + ConverterBase& operator=(ConverterBase&&) = delete; + + const T* CPointer() const { return &m_data; } + T m_data{}; + }; // UpstreamConfiguration inline void CppToC(const UpstreamConfiguration& data, HermesUpstreamConfiguration& result) { - CppToC(data.m_upstreamLaneId, result.m_upstreamLaneId); + CppToC(data.m_upstreamLaneId, result.m_upstreamLaneId); CppToC(data.m_optionalUpstreamInterfaceId, result.m_optionalUpstreamInterfaceId); - CppToC(data.m_hostAddress, result.m_hostAddress); - CppToC(data.m_port, result.m_port); + CppToC(data.m_hostAddress, result.m_hostAddress); + CppToC(data.m_port, result.m_port); } - inline void CToCpp(const HermesUpstreamConfiguration& data, UpstreamConfiguration& result) { - CToCpp(data.m_upstreamLaneId, result.m_upstreamLaneId); + CToCpp(data.m_upstreamLaneId, result.m_upstreamLaneId); CToCpp(data.m_optionalUpstreamInterfaceId, result.m_optionalUpstreamInterfaceId); - CToCpp(data.m_port, result.m_port); - CToCpp(data.m_hostAddress, result.m_hostAddress); + CToCpp(data.m_hostAddress, result.m_hostAddress); + CToCpp(data.m_port, result.m_port); } // DownstreamConfiguration inline void CppToC(const DownstreamConfiguration& data, HermesDownstreamConfiguration& result) { - CppToC(data.m_downstreamLaneId, result.m_downstreamLaneId); + CppToC(data.m_downstreamLaneId, result.m_downstreamLaneId); CppToC(data.m_optionalDownstreamInterfaceId, result.m_optionalDownstreamInterfaceId); - CppToC(data.m_optionalClientAddress, result.m_optionalClientAddress); - CppToC(data.m_port, result.m_port); + CppToC(data.m_optionalClientAddress, result.m_optionalClientAddress); + CppToC(data.m_port, result.m_port); } - inline void CToCpp(const HermesDownstreamConfiguration& data, DownstreamConfiguration& result) { - CToCpp(data.m_downstreamLaneId, result.m_downstreamLaneId); + CToCpp(data.m_downstreamLaneId, result.m_downstreamLaneId); CToCpp(data.m_optionalDownstreamInterfaceId, result.m_optionalDownstreamInterfaceId); - CToCpp(data.m_optionalClientAddress, result.m_optionalClientAddress); - CToCpp(data.m_port, result.m_port); + CToCpp(data.m_optionalClientAddress, result.m_optionalClientAddress); + CToCpp(data.m_port, result.m_port); } - template - struct Converter2C; - - template - struct ConverterBase - { - ConverterBase() = default; - ConverterBase(const ConverterBase&) = delete; - ConverterBase(ConverterBase&&) = delete; - ConverterBase& operator=(const ConverterBase&) = delete; - ConverterBase& operator=(ConverterBase&&) = delete; - - const T* CPointer() const { return &m_data; } - T m_data{}; - }; - + // SetConfigurationData template<> struct Converter2C : ConverterBase { explicit Converter2C(const SetConfigurationData& data) { - CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_machineId, m_data.m_machineId); CppToC(data.m_optionalSupervisorySystemPort, m_data.m_pOptionalSupervisorySystemPort); - CppToC(data.m_upstreamConfigurations, m_upstreamConfigurations, m_data.m_upstreamConfigurations); - CppToC(data.m_downstreamConfigurations, m_downstreamConfigurations, m_data.m_downstreamConfigurations); + CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); + CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); } - private: - VectorHolder m_upstreamConfigurations; - VectorHolder m_downstreamConfigurations; + VectorHolder m_upstreamHolder; + VectorHolder m_downstreamHolder; }; - inline SetConfigurationData ToCpp(const HermesSetConfigurationData& data) { SetConfigurationData result; - CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_machineId, result.m_machineId); CToCpp(data.m_pOptionalSupervisorySystemPort, result.m_optionalSupervisorySystemPort); - CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); - CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); + CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); + CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); return result; } // GetConfigurationData template<> struct Converter2C : ConverterBase - { - explicit Converter2C(const GetConfigurationData&) {} - }; - inline GetConfigurationData ToCpp(const HermesGetConfigurationData&) { return{}; } + { explicit Converter2C(const GetConfigurationData&) {} }; + inline GetConfigurationData ToCpp(const HermesGetConfigurationData&) { return {}; } // CurrentConfigurationData template<> @@ -294,24 +322,22 @@ namespace Hermes { explicit Converter2C(const CurrentConfigurationData& data) { - CppToC(data.m_optionalMachineId, m_data.m_optionalMachineId); + CppToC(data.m_optionalMachineId, m_data.m_optionalMachineId); CppToC(data.m_optionalSupervisorySystemPort, m_data.m_pOptionalSupervisorySystemPort); - CppToC(data.m_upstreamConfigurations, m_upstreamConfigurations, m_data.m_upstreamConfigurations); - CppToC(data.m_downstreamConfigurations, m_downstreamConfigurations, m_data.m_downstreamConfigurations); + CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); + CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); } - private: - VectorHolder m_upstreamConfigurations; - VectorHolder m_downstreamConfigurations; + VectorHolder m_upstreamHolder; + VectorHolder m_downstreamHolder; }; - inline CurrentConfigurationData ToCpp(const HermesCurrentConfigurationData& data) { CurrentConfigurationData result; - CToCpp(data.m_optionalMachineId, result.m_optionalMachineId); + CToCpp(data.m_optionalMachineId, result.m_optionalMachineId); CToCpp(data.m_pOptionalSupervisorySystemPort, result.m_optionalSupervisorySystemPort); - CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); - CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); + CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); + CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); return result; } @@ -319,277 +345,57 @@ namespace Hermes template<> struct Converter2C : ConverterBase { - Converter2C(const ConnectionInfo& data) + explicit Converter2C(const ConnectionInfo& data) { - CppToC(data.m_address, m_data.m_address); - CppToC(data.m_port, m_data.m_port); + CppToC(data.m_address, m_data.m_address); + CppToC(data.m_port, m_data.m_port); CppToC(data.m_hostName, m_data.m_hostName); } }; inline ConnectionInfo ToCpp(const HermesConnectionInfo& data) { ConnectionInfo result; - CToCpp(data.m_address, result.m_address); - CToCpp(data.m_port, result.m_port); + CToCpp(data.m_address, result.m_address); + CToCpp(data.m_port, result.m_port); CToCpp(data.m_hostName, result.m_hostName); return result; } - // Features - inline void CppToC(const FeatureBoardForecast&, HermesFeatureBoardForecast&) {} - inline void CppToC(const FeatureCheckAliveResponse&, HermesFeatureCheckAliveResponse&) {} - inline void CppToC(const FeatureQueryBoardInfo&, HermesFeatureQueryBoardInfo&) {} - inline void CppToC(const FeatureSendBoardInfo&, HermesFeatureSendBoardInfo&) {} - inline void CppToC(const FeatureCommand&, HermesFeatureCommand&) {} - - inline void CToCpp(const HermesFeatureBoardForecast&, FeatureBoardForecast&) {} - inline void CToCpp(const HermesFeatureCheckAliveResponse&, FeatureCheckAliveResponse&) {} - inline void CToCpp(const HermesFeatureQueryBoardInfo&, FeatureQueryBoardInfo&) {} - inline void CToCpp(const HermesFeatureSendBoardInfo&, FeatureSendBoardInfo&) {} - inline void CToCpp(const HermesFeatureCommand&, FeatureCommand&) {} - - // SupportedFeatures - struct SupportedFeaturesHolder : ConverterBase - { - HermesFeatureBoardForecast m_optionalFeatureBoardForecast; - HermesFeatureCheckAliveResponse m_optionalFeatureCheckAliveResponse; - HermesFeatureQueryBoardInfo m_optionalFeatureQueryBoardInfo; - HermesFeatureSendBoardInfo m_optionalFeatureSendBoardInfo; - HermesFeatureCommand m_optionalFeatureCommand; - }; - - inline void CppToC(const SupportedFeatures& data, SupportedFeaturesHolder& result) - { - auto& hermesData = result.m_data; - CppToC(data.m_optionalFeatureBoardForecast, result.m_optionalFeatureBoardForecast, hermesData.m_pOptionalFeatureBoardForecast); - CppToC(data.m_optionalFeatureCheckAliveResponse, result.m_optionalFeatureCheckAliveResponse, hermesData.m_pOptionalFeatureCheckAliveResponse); - CppToC(data.m_optionalFeatureQueryBoardInfo, result.m_optionalFeatureQueryBoardInfo, hermesData.m_pOptionalFeatureQueryBoardInfo); - CppToC(data.m_optionalFeatureSendBoardInfo, result.m_optionalFeatureSendBoardInfo, hermesData.m_pOptionalFeatureSendBoardInfo); - CppToC(data.m_optionalFeatureCommand, result.m_optionalFeatureCommand, hermesData.m_pOptionalFeatureCommand); - } - - inline void CToCpp(const HermesSupportedFeatures& data, SupportedFeatures& result) - { - CToCpp(data.m_pOptionalFeatureBoardForecast, result.m_optionalFeatureBoardForecast); - CToCpp(data.m_pOptionalFeatureCheckAliveResponse, result.m_optionalFeatureCheckAliveResponse); - CToCpp(data.m_pOptionalFeatureQueryBoardInfo, result.m_optionalFeatureQueryBoardInfo); - CToCpp(data.m_pOptionalFeatureSendBoardInfo, result.m_optionalFeatureSendBoardInfo); - CToCpp(data.m_pOptionalFeatureCommand, result.m_optionalFeatureCommand); - } - - // ServiceDescriptionData + // FIX: Was Converter2C — typo, must be Converter2C template<> - struct Converter2C : ConverterBase - { - explicit Converter2C(const ServiceDescriptionData& data) - { - CppToC(data.m_machineId, m_data.m_machineId); - CppToC(data.m_laneId, m_data.m_laneId); - CppToC(data.m_optionalInterfaceId, m_data.m_optionalInterfaceId); - CppToC(data.m_version, m_data.m_version); - CppToC(data.m_supportedFeatures, m_supportedFeatures); - m_data.m_pSupportedFeatures = &m_supportedFeatures.m_data; - } - - private: - SupportedFeaturesHolder m_supportedFeatures; - }; - inline ServiceDescriptionData ToCpp(const HermesServiceDescriptionData& data) - { - ServiceDescriptionData result; - CToCpp(data.m_machineId, result.m_machineId); - CToCpp(data.m_laneId, result.m_laneId); - CToCpp(data.m_optionalInterfaceId, result.m_optionalInterfaceId); - CToCpp(data.m_version, result.m_version); - CToCpp(data.m_pSupportedFeatures, result.m_supportedFeatures); - return result; - } - - // MachineReadyData - template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - explicit Converter2C(const MachineReadyData& data) + explicit Converter2C(const Error& data) { - CppToC(data.m_optionalFlippedBoard, m_flippedBoard, m_data.m_pOptionalFlippedBoard); - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalForecastId, m_data.m_optionalForecastId); - CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_optionalLengthInMM, m_data.m_pOptionalLengthInMM); - CppToC(data.m_optionalWidthInMM, m_data.m_pOptionalWidthInMM); - CppToC(data.m_optionalThicknessInMM, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_optionalTopClearanceHeightInMM, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_optionalBottomClearanceHeightInMM, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_optionalWeightInGrams, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_code, m_data.m_code); + CppToC(data.m_text, m_data.m_text); } - - private: - EHermesFlippedBoard m_flippedBoard; }; - - inline MachineReadyData ToCpp(const HermesMachineReadyData& data) + inline Error ToCpp(const HermesError& data) { - MachineReadyData result; - CToCpp(data.m_failedBoard, result.m_failedBoard); - CToCpp(data.m_optionalForecastId, result.m_optionalForecastId); - CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); - CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); - CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_optionalBottomClearanceHeightInMM); - CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); - CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + Error result; + CToCpp(data.m_code, result.m_code); + CToCpp(data.m_text, result.m_text); return result; } - // RevokeMachineReadyData - template<> struct Converter2C : ConverterBase - { - explicit Converter2C(const RevokeMachineReadyData&) {} - }; - inline RevokeMachineReadyData ToCpp(const HermesRevokeMachineReadyData&) { return{}; } - - // SubBoards - inline void CppToC(const SubBoard& data, HermesSubBoard& result) - { - CppToC(data.m_pos, result.m_pos); - CppToC(data.m_optionalBc, result.m_optionalBc); - CppToC(data.m_st, result.m_st); - } - - inline void CToCpp(const HermesSubBoard& data, SubBoard& result) - { - CToCpp(data.m_pos, result.m_pos); - CToCpp(data.m_optionalBc, result.m_optionalBc); - CToCpp(data.m_st, result.m_st); - } - - // BoardAvailableData + // CheckAliveData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - Converter2C(const BoardAvailableData& data) + explicit Converter2C(const CheckAliveData& data) { - CppToC(data.m_boardId, m_data.m_boardId); - CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_flippedBoard, m_data.m_flippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_optionalLengthInMM, m_data.m_pOptionalLengthInMM); - CppToC(data.m_optionalWidthInMM, m_data.m_pOptionalWidthInMM); - CppToC(data.m_optionalThicknessInMM, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_optionalTopClearanceHeightInMM, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_optionalBottomClearanceHeightInMM, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_optionalWeightInGrams, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); - CppToC(data.m_optionalRoute, m_data.m_pOptionalRoute); - CppToC(data.m_optionalAction, m_data.m_pOptionalAction); - CppToC(data.m_optionalSubBoards, m_subBoards, m_data.m_optionalSubBoards); + CppToC(data.m_optionalType, m_optionalType, m_data.m_pOptionalType); + CppToC(data.m_optionalId, m_data.m_optionalId); } - private: - VectorHolder m_subBoards; - }; - inline BoardAvailableData ToCpp(const HermesBoardAvailableData& data) - { - BoardAvailableData result; - CToCpp(data.m_boardId, result.m_boardId); - CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); - CToCpp(data.m_failedBoard, result.m_failedBoard); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_flippedBoard, result.m_flippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); - CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); - CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_optionalBottomClearanceHeightInMM); - CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); - CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); - CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); - CToCpp(data.m_pOptionalAction, result.m_optionalAction); - CToCpp(data.m_optionalSubBoards, result.m_optionalSubBoards); - return result; - } - - // RevokeBoardAvailableData - template<> struct Converter2C : ConverterBase - { - explicit Converter2C(const RevokeBoardAvailableData&) {} - }; - inline RevokeBoardAvailableData ToCpp(const HermesRevokeBoardAvailableData&) { return{}; } - - // StartTransportData - template<> - struct Converter2C : ConverterBase - { - Converter2C(const StartTransportData& data) - { - CppToC(data.m_boardId, m_data.m_boardId); - CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - } - }; - inline StartTransportData ToCpp(const HermesStartTransportData& data) - { - StartTransportData result; - CToCpp(data.m_boardId, result.m_boardId); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); - return result; - } - - // StopTransportData - template<> - struct Converter2C : ConverterBase - { - Converter2C(const StopTransportData& data) - { - CppToC(data.m_transferState, m_data.m_transferState); - CppToC(data.m_boardId, m_data.m_boardId); - } - }; - inline StopTransportData ToCpp(const HermesStopTransportData& data) - { - StopTransportData result; - CToCpp(data.m_transferState, result.m_transferState); - CToCpp(data.m_boardId, result.m_boardId); - return result; - } - - // TransportFinishedData - template<> - struct Converter2C : ConverterBase - { - Converter2C(const TransportFinishedData& data) - { - CppToC(data.m_transferState, m_data.m_transferState); - CppToC(data.m_boardId, m_data.m_boardId); - } + EHermesCheckAliveType m_optionalType{}; }; - inline TransportFinishedData ToCpp(const HermesTransportFinishedData& data) + inline CheckAliveData ToCpp(const HermesCheckAliveData& data) { - TransportFinishedData result; - CToCpp(data.m_transferState, result.m_transferState); - CToCpp(data.m_boardId, result.m_boardId); + CheckAliveData result; + CToCpp(data.m_pOptionalType, result.m_optionalType); + CToCpp(data.m_optionalId, result.m_optionalId); return result; } @@ -597,485 +403,263 @@ namespace Hermes template<> struct Converter2C : ConverterBase { - Converter2C(const NotificationData& data) + explicit Converter2C(const NotificationData& data) { CppToC(data.m_notificationCode, m_data.m_notificationCode); - CppToC(data.m_severity, m_data.m_severity); - CppToC(data.m_description, m_data.m_description); + CppToC(data.m_severity, m_data.m_severity); + CppToC(data.m_description, m_data.m_description); } }; inline NotificationData ToCpp(const HermesNotificationData& data) { NotificationData result; CToCpp(data.m_notificationCode, result.m_notificationCode); - CToCpp(data.m_severity, result.m_severity); - CToCpp(data.m_description, result.m_description); + CToCpp(data.m_severity, result.m_severity); + CToCpp(data.m_description, result.m_description); return result; } - // CheckAliveData + // ServiceDescriptionData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - Converter2C(const CheckAliveData& data) + explicit Converter2C(const ServiceDescriptionData& data) { - CppToC(data.m_optionalType, m_optionalType, m_data.m_pOptionalType); - CppToC(data.m_optionalId, m_data.m_optionalId); + CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_laneId, m_data.m_laneId); + CppToC(data.m_optionalInterfaceId, m_data.m_optionalInterfaceId); + CppToC(data.m_version, m_data.m_version); + m_data.m_pSupportedFeatures = nullptr; // features are optional, not mapped here } - - private: - EHermesCheckAliveType m_optionalType; }; - inline CheckAliveData ToCpp(const HermesCheckAliveData& data) + inline ServiceDescriptionData ToCpp(const HermesServiceDescriptionData& data) { - CheckAliveData result; - CToCpp(data.m_pOptionalType, result.m_optionalType); - CToCpp(data.m_optionalId, result.m_optionalId); + ServiceDescriptionData result; + CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_laneId, result.m_laneId); + CToCpp(data.m_optionalInterfaceId, result.m_optionalInterfaceId); + CToCpp(data.m_version, result.m_version); return result; } - // BoardForecastData - template<> - struct Converter2C : ConverterBase + // SubBoard / SubBoards helper + struct SubBoardsHolder { - Converter2C(const BoardForecastData& data) - { - CppToC(data.m_optionalForecastId, m_data.m_optionalForecastId); - CppToC(data.m_optionalTimeUntilAvailableInSeconds, m_data.m_pOptionalTimeUntilAvailableInSeconds); - CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); - CppToC(data.m_optionalBoardIdCreatedBy, m_data.m_optionalBoardIdCreatedBy); - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_flippedBoard, m_data.m_flippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_optionalLengthInMM, m_data.m_pOptionalLengthInMM); - CppToC(data.m_optionalWidthInMM, m_data.m_pOptionalWidthInMM); - CppToC(data.m_optionalThicknessInMM, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_optionalTopClearanceHeightInMM, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_optionalBottomClearanceHeightInMM, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_optionalWeightInGrams, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); - } + VectorHolder m_holder; + HermesSubBoards m_data{}; }; - inline BoardForecastData ToCpp(const HermesBoardForecastData& data) - { - BoardForecastData result; - CToCpp(data.m_optionalForecastId, result.m_optionalForecastId); - CToCpp(data.m_pOptionalTimeUntilAvailableInSeconds, result.m_optionalTimeUntilAvailableInSeconds); - CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); - CToCpp(data.m_optionalBoardIdCreatedBy, result.m_optionalBoardIdCreatedBy); - CToCpp(data.m_failedBoard, result.m_failedBoard); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_flippedBoard, result.m_flippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); - CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); - CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_optionalBottomClearanceHeightInMM); - CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); - CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); - return result; - } - // QueryBoardInfoData - template<> - struct Converter2C : ConverterBase - { - Converter2C(const QueryBoardInfoData& data) - { - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - } - }; - inline QueryBoardInfoData ToCpp(const HermesQueryBoardInfoData& data) + inline void CppToC(const SubBoard& data, HermesSubBoard& result) { - QueryBoardInfoData result; - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - return result; + CppToC(data.m_pos, result.m_pos); + CppToC(data.m_optionalBc, result.m_optionalBc); + CppToC(data.m_st, result.m_st); } - - // SendBoardInfoData - template<> - struct Converter2C : ConverterBase - { - Converter2C(const SendBoardInfoData& data) - { - CppToC(data.m_optionalFailedBoard, m_optionalFailedBoard, m_data.m_pOptionalFailedBoard); - CppToC(data.m_optionalFlippedBoard, m_optionalFlippedBoard, m_data.m_pOptionalFlippedBoard); - - CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); - CppToC(data.m_optionalBoardIdCreatedBy, m_data.m_optionalBoardIdCreatedBy); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_optionalLengthInMM, m_data.m_pOptionalLengthInMM); - CppToC(data.m_optionalWidthInMM, m_data.m_pOptionalWidthInMM); - CppToC(data.m_optionalThicknessInMM, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_optionalTopClearanceHeightInMM, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_optionalBottomClearanceHeightInMM, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_optionalWeightInGrams, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); - CppToC(data.m_optionalRoute, m_data.m_pOptionalRoute); - CppToC(data.m_optionalAction, m_data.m_pOptionalAction); - CppToC(data.m_optionalSubBoards, m_subBoards, m_data.m_optionalSubBoards); - } - - private: - EHermesBoardQuality m_optionalFailedBoard; - EHermesFlippedBoard m_optionalFlippedBoard; - VectorHolder m_subBoards; - }; - - inline SendBoardInfoData ToCpp(const HermesSendBoardInfoData& data) + inline void CToCpp(const HermesSubBoard& data, SubBoard& result) { - SendBoardInfoData result; - CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); - CToCpp(data.m_optionalBoardIdCreatedBy, result.m_optionalBoardIdCreatedBy); - CToCpp(data.m_pOptionalFailedBoard, result.m_optionalFailedBoard); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); - CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); - CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_optionalBottomClearanceHeightInMM); - CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); - CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); - CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); - CToCpp(data.m_pOptionalAction, result.m_optionalAction); - CToCpp(data.m_optionalSubBoards, result.m_optionalSubBoards); - return result; + CToCpp(data.m_pos, result.m_pos); + CToCpp(data.m_optionalBc, result.m_optionalBc); + CToCpp(data.m_st, result.m_st); } - // SupervisoryFeatures - inline void CppToC(const FeatureConfiguration&, HermesFeatureConfiguration&) {} - inline void CppToC(const FeatureBoardTracking&, HermesFeatureBoardTracking&) {} - inline void CppToC(const FeatureQueryWorkOrderInfo&, HermesFeatureQueryWorkOrderInfo&) {} - inline void CppToC(const FeatureSendWorkOrderInfo&, HermesFeatureSendWorkOrderInfo&) {} - inline void CppToC(const FeatureReplyWorkOrderInfo&, HermesFeatureReplyWorkOrderInfo&) {} - inline void CppToC(const FeatureQueryHermesCapabilities&, HermesFeatureQueryHermesCapabilities&) {} - inline void CppToC(const FeatureSendHermesCapabilities&, HermesFeatureSendHermesCapabilities&) {} - - inline void CToCpp(const HermesFeatureConfiguration&, FeatureConfiguration&) {} - inline void CToCpp(const HermesFeatureBoardTracking&, FeatureBoardTracking&) {} - inline void CToCpp(const HermesFeatureQueryWorkOrderInfo&, FeatureQueryWorkOrderInfo&) {} - inline void CToCpp(const HermesFeatureSendWorkOrderInfo&, FeatureSendWorkOrderInfo&) {} - inline void CToCpp(const HermesFeatureReplyWorkOrderInfo&, FeatureReplyWorkOrderInfo&) {} - inline void CToCpp(const HermesFeatureQueryHermesCapabilities&, FeatureQueryHermesCapabilities&) {} - inline void CToCpp(const HermesFeatureSendHermesCapabilities&, FeatureSendHermesCapabilities&) {} - - // SupervisoryFeatures - struct SupervisoryFeaturesHolder : ConverterBase - { - HermesFeatureConfiguration m_optionalFeatureConfiguration; - HermesFeatureCheckAliveResponse m_optionalFeatureCheckAliveResponse; - HermesFeatureBoardTracking m_optionalFeatureBoardTracking; - HermesFeatureQueryWorkOrderInfo m_optionalFeatureQueryWorkOrderInfo; - HermesFeatureSendWorkOrderInfo m_optionalFeatureSendWorkOrderInfo; - HermesFeatureReplyWorkOrderInfo m_optionalFeatureReplyWorkOrderInfo; - HermesFeatureQueryHermesCapabilities m_optionalFeatureQueryHermesCapabilities; - HermesFeatureSendHermesCapabilities m_optionalFeatureSendHermesCapabilities; - }; - - inline void CppToC(const SupervisoryFeatures& data, SupervisoryFeaturesHolder& intermediate) + inline void CppToC(const std::vector& data, SubBoardsHolder& holder) { - auto& hermesData = intermediate.m_data; - CppToC(data.m_optionalFeatureConfiguration, intermediate.m_optionalFeatureConfiguration, hermesData.m_pOptionalFeatureConfiguration); - CppToC(data.m_optionalFeatureCheckAliveResponse, intermediate.m_optionalFeatureCheckAliveResponse, hermesData.m_pOptionalFeatureCheckAliveResponse); - CppToC(data.m_optionalFeatureBoardTracking, intermediate.m_optionalFeatureBoardTracking, hermesData.m_pOptionalFeatureBoardTracking); - CppToC(data.m_optionalFeatureQueryWorkOrderInfo, intermediate.m_optionalFeatureQueryWorkOrderInfo, hermesData.m_pOptionalFeatureQueryWorkOrderInfo); - CppToC(data.m_optionalFeatureSendWorkOrderInfo, intermediate.m_optionalFeatureSendWorkOrderInfo, hermesData.m_pOptionalFeatureSendWorkOrderInfo); - CppToC(data.m_optionalFeatureReplyWorkOrderInfo, intermediate.m_optionalFeatureReplyWorkOrderInfo, hermesData.m_pOptionalFeatureReplyWorkOrderInfo); - CppToC(data.m_optionalFeatureQueryHermesCapabilities, intermediate.m_optionalFeatureQueryHermesCapabilities, hermesData.m_pOptionalFeatureQueryHermesCapabilities); - CppToC(data.m_optionalFeatureSendHermesCapabilities, intermediate.m_optionalFeatureSendHermesCapabilities, hermesData.m_pOptionalFeatureSendHermesCapabilities); + CppToC(data, holder.m_holder, holder.m_data); } - - inline void CToCpp(const HermesSupervisoryFeatures& data, SupervisoryFeatures& result) + inline void CToCpp(const HermesSubBoards& data, std::vector& result) { - CToCpp(data.m_pOptionalFeatureConfiguration, result.m_optionalFeatureConfiguration); - CToCpp(data.m_pOptionalFeatureCheckAliveResponse, result.m_optionalFeatureCheckAliveResponse); - CToCpp(data.m_pOptionalFeatureBoardTracking, result.m_optionalFeatureBoardTracking); - CToCpp(data.m_pOptionalFeatureQueryWorkOrderInfo, result.m_optionalFeatureQueryWorkOrderInfo); - CToCpp(data.m_pOptionalFeatureSendWorkOrderInfo, result.m_optionalFeatureSendWorkOrderInfo); - CToCpp(data.m_pOptionalFeatureReplyWorkOrderInfo, result.m_optionalFeatureReplyWorkOrderInfo); - CToCpp(data.m_pOptionalFeatureQueryHermesCapabilities, result.m_optionalFeatureQueryHermesCapabilities); - CToCpp(data.m_pOptionalFeatureSendHermesCapabilities, result.m_optionalFeatureSendHermesCapabilities); + CToCpp(data, result); } - // SupervisoryServiceDescriptionData + // BoardAvailableData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - Converter2C(const SupervisoryServiceDescriptionData& data) + explicit Converter2C(const BoardAvailableData& data) { - CppToC(data.m_systemId, m_data.m_systemId); - CppToC(data.m_version, m_data.m_version); - CppToC(data.m_supportedFeatures, m_supervisoryFeatures); - m_data.m_pSupportedFeatures = &m_supervisoryFeatures.m_data; + CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_flippedBoard, m_data.m_flippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_length, m_optLength, m_data.m_pOptionalLengthInMM); + CppToC(data.m_width, m_optWidth, m_data.m_pOptionalWidthInMM); + CppToC(data.m_thickness, m_optThick, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_topClearanceHeight, m_optTopClear, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_bottomClearanceHeight, m_optBotClear, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_weight, m_optWeight, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_optionalRoute, m_optRoute, m_data.m_pOptionalRoute); + CppToC(data.m_optionalAction, m_optAction, m_data.m_pOptionalAction); + CppToC(data.m_subBoards, m_subBoardsHolder); + m_data.m_optionalSubBoards = m_subBoardsHolder.m_data; } private: - SupervisoryFeaturesHolder m_supervisoryFeatures; + double m_optLength{}, m_optWidth{}, m_optThick{}, m_optSpeed{}; + double m_optTopClear{}, m_optBotClear{}, m_optWeight{}; + uint16_t m_optRoute{}, m_optAction{}; + SubBoardsHolder m_subBoardsHolder; }; - - // SupervisoryServiceDescriptionData - inline SupervisoryServiceDescriptionData ToCpp(const HermesSupervisoryServiceDescriptionData& data) + inline BoardAvailableData ToCpp(const HermesBoardAvailableData& data) { - SupervisoryServiceDescriptionData result; - CToCpp(data.m_systemId, result.m_systemId); - CToCpp(data.m_version, result.m_version); - CToCpp(data.m_pSupportedFeatures, result.m_supportedFeatures); + BoardAvailableData result; + CToCpp(data.m_boardId, result.m_boardId); + CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); + CToCpp(data.m_failedBoard, result.m_failedBoard); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_flippedBoard, result.m_flippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_length); + CToCpp(data.m_pOptionalWidthInMM, result.m_width); + CToCpp(data.m_pOptionalThicknessInMM, result.m_thickness); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_topClearanceHeight); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_bottomClearanceHeight); + CToCpp(data.m_pOptionalWeightInGrams, result.m_weight); + CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); + CToCpp(data.m_pOptionalAction, result.m_optionalAction); + CToCpp(data.m_optionalSubBoards, result.m_subBoards); return result; } - // BoardArrivedData + // RevokeBoardAvailableData + template<> struct Converter2C : ConverterBase + { explicit Converter2C(const RevokeBoardAvailableData&) {} }; + inline RevokeBoardAvailableData ToCpp(const HermesRevokeBoardAvailableData&) { return {}; } + + // MachineReadyData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - Converter2C(const BoardArrivedData& data) + explicit Converter2C(const MachineReadyData& data) { - CppToC(data.m_machineId, m_data.m_machineId); - CppToC(data.m_upstreamLaneId, m_data.m_upstreamLaneId); - CppToC(data.m_optionalUpstreamInterfaceId, m_data.m_optionalUpstreamInterfaceId); - CppToC(data.m_optionalMagazineId, m_data.m_optionalMagazineId); - CppToC(data.m_optionalSlotId, m_data.m_pOptionalSlotId); - CppToC(data.m_boardTransfer, m_data.m_boardTransfer); - CppToC(data.m_boardId, m_data.m_boardId); - CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_flippedBoard, m_data.m_flippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_optionalLengthInMM, m_data.m_pOptionalLengthInMM); - CppToC(data.m_optionalWidthInMM, m_data.m_pOptionalWidthInMM); - CppToC(data.m_optionalThicknessInMM, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_optionalTopClearanceHeightInMM, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_optionalBottomClearanceHeightInMM, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_optionalWeightInGrams, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalForecastId, m_data.m_optionalForecastId); + CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_optionalFlippedBoard, m_optFlipped, m_data.m_pOptionalFlippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_length, m_optLength, m_data.m_pOptionalLengthInMM); + CppToC(data.m_width, m_optWidth, m_data.m_pOptionalWidthInMM); + CppToC(data.m_thickness, m_optThick, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_topClearanceHeight, m_optTopClear, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_bottomClearanceHeight, m_optBotClear, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_weight, m_optWeight, m_data.m_pOptionalWeightInGrams); CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); - CppToC(data.m_optionalRoute, m_data.m_pOptionalRoute); - CppToC(data.m_optionalAction, m_data.m_pOptionalAction); - CppToC(data.m_optionalSubBoards, m_subBoards, m_data.m_optionalSubBoards); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); } private: - VectorHolder m_subBoards; + EHermesFlippedBoard m_optFlipped{}; + double m_optLength{}, m_optWidth{}, m_optThick{}, m_optSpeed{}; + double m_optTopClear{}, m_optBotClear{}, m_optWeight{}; }; - inline BoardArrivedData ToCpp(const HermesBoardArrivedData& data) + inline MachineReadyData ToCpp(const HermesMachineReadyData& data) { - BoardArrivedData result; - CToCpp(data.m_machineId, result.m_machineId); - CToCpp(data.m_upstreamLaneId, result.m_upstreamLaneId); - CToCpp(data.m_optionalUpstreamInterfaceId, result.m_optionalUpstreamInterfaceId); - CToCpp(data.m_optionalMagazineId, result.m_optionalMagazineId); - CToCpp(data.m_pOptionalSlotId, result.m_optionalSlotId); - CToCpp(data.m_boardTransfer, result.m_boardTransfer); - CToCpp(data.m_boardId, result.m_boardId); - CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); - CToCpp(data.m_failedBoard, result.m_failedBoard); + MachineReadyData result; + CToCpp(data.m_failedBoard, result.m_failedBoard); + CToCpp(data.m_optionalForecastId, result.m_optionalForecastId); + CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_flippedBoard, result.m_flippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); - CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); - CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_optionalBottomClearanceHeightInMM); - CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); - CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); - CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); - CToCpp(data.m_pOptionalAction, result.m_optionalAction); - CToCpp(data.m_optionalSubBoards, result.m_optionalSubBoards); + CToCpp(data.m_pOptionalLengthInMM, result.m_length); + CToCpp(data.m_pOptionalWidthInMM, result.m_width); + CToCpp(data.m_pOptionalThicknessInMM, result.m_thickness); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_topClearanceHeight); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM,result.m_bottomClearanceHeight); + CToCpp(data.m_pOptionalWeightInGrams, result.m_weight); + CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); return result; } - // BoardDepartedData + // RevokeMachineReadyData + template<> struct Converter2C : ConverterBase + { explicit Converter2C(const RevokeMachineReadyData&) {} }; + inline RevokeMachineReadyData ToCpp(const HermesRevokeMachineReadyData&) { return {}; } + + // StartTransportData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - Converter2C(const BoardDepartedData& data) + explicit Converter2C(const StartTransportData& data) { - CppToC(data.m_machineId, m_data.m_machineId); - CppToC(data.m_downstreamLaneId, m_data.m_downstreamLaneId); - CppToC(data.m_optionalDownstreamInterfaceId, m_data.m_optionalDownstreamInterfaceId); - CppToC(data.m_optionalMagazineId, m_data.m_optionalMagazineId); - CppToC(data.m_optionalSlotId, m_data.m_pOptionalSlotId); - CppToC(data.m_boardTransfer, m_data.m_boardTransfer); - CppToC(data.m_boardId, m_data.m_boardId); - CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_flippedBoard, m_data.m_flippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_optionalLengthInMM, m_data.m_pOptionalLengthInMM); - CppToC(data.m_optionalWidthInMM, m_data.m_pOptionalWidthInMM); - CppToC(data.m_optionalThicknessInMM, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_optionalTopClearanceHeightInMM, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_optionalBottomClearanceHeightInMM, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_optionalWeightInGrams, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); - CppToC(data.m_optionalRoute, m_data.m_pOptionalRoute); - CppToC(data.m_optionalAction, m_data.m_pOptionalAction); - CppToC(data.m_optionalSubBoards, m_subBoards, m_data.m_optionalSubBoards); + CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); } private: - VectorHolder m_subBoards; + double m_optSpeed{}; }; - - inline BoardDepartedData ToCpp(const HermesBoardDepartedData& data) + inline StartTransportData ToCpp(const HermesStartTransportData& data) { - BoardDepartedData result; - - CToCpp(data.m_machineId, result.m_machineId); - CToCpp(data.m_downstreamLaneId, result.m_downstreamLaneId); - CToCpp(data.m_optionalDownstreamInterfaceId, result.m_optionalDownstreamInterfaceId); - CToCpp(data.m_optionalMagazineId, result.m_optionalMagazineId); - CToCpp(data.m_pOptionalSlotId, result.m_optionalSlotId); - CToCpp(data.m_boardTransfer, result.m_boardTransfer); - CToCpp(data.m_boardId, result.m_boardId); - CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); - CToCpp(data.m_failedBoard, result.m_failedBoard); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_flippedBoard, result.m_flippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); - CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); - CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_optionalBottomClearanceHeightInMM); - CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); - CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); - CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); - CToCpp(data.m_pOptionalAction, result.m_optionalAction); - CToCpp(data.m_optionalSubBoards, result.m_optionalSubBoards); + StartTransportData result; + CToCpp(data.m_boardId, result.m_boardId); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); return result; } - // QueryWorkOrderInfoData + // StopTransportData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - Converter2C(const QueryWorkOrderInfoData& data) + explicit Converter2C(const StopTransportData& data) { - CppToC(data.m_optionalQueryId, m_data.m_optionalQueryId); - CppToC(data.m_machineId, m_data.m_machineId); - CppToC(data.m_optionalMagazineId, m_data.m_optionalMagazineId); - CppToC(data.m_optionalSlotId, m_data.m_pOptionalSlotId); - CppToC(data.m_optionalBarcode, m_data.m_optionalBarcode); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_transferState, m_data.m_transferState); + CppToC(data.m_boardId, m_data.m_boardId); } }; - inline QueryWorkOrderInfoData ToCpp(const HermesQueryWorkOrderInfoData& data) + inline StopTransportData ToCpp(const HermesStopTransportData& data) { - QueryWorkOrderInfoData result; - CToCpp(data.m_optionalQueryId, result.m_optionalQueryId); - CToCpp(data.m_machineId, result.m_machineId); - CToCpp(data.m_optionalMagazineId, result.m_optionalMagazineId); - CToCpp(data.m_pOptionalSlotId, result.m_optionalSlotId); - CToCpp(data.m_optionalBarcode, result.m_optionalBarcode); - CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + StopTransportData result; + CToCpp(data.m_transferState, result.m_transferState); + CToCpp(data.m_boardId, result.m_boardId); return result; } - // SendWorkOrderInfoData + // TransportFinishedData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - Converter2C(const SendWorkOrderInfoData& data) + explicit Converter2C(const TransportFinishedData& data) { - CppToC(data.m_optionalQueryId, m_data.m_optionalQueryId); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); - CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); - CppToC(data.m_optionalBoardIdCreatedBy, m_data.m_optionalBoardIdCreatedBy); - CppToC(data.m_optionalFailedBoard, m_optionalFailedBoard, m_data.m_pOptionalFailedBoard); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_optionalFlippedBoard, m_optionalFlippedBoard, m_data.m_pOptionalFlippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_optionalLengthInMM, m_data.m_pOptionalLengthInMM); - CppToC(data.m_optionalWidthInMM, m_data.m_pOptionalWidthInMM); - CppToC(data.m_optionalThicknessInMM, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_optionalTopClearanceHeightInMM, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_optionalBottomClearanceHeightInMM, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_optionalWeightInGrams, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalRoute, m_data.m_pOptionalRoute); - CppToC(data.m_optionalSubBoards, m_subBoards, m_data.m_optionalSubBoards); + CppToC(data.m_transferState, m_data.m_transferState); + CppToC(data.m_boardId, m_data.m_boardId); } - private: - VectorHolder m_subBoards; - EHermesBoardQuality m_optionalFailedBoard; - EHermesFlippedBoard m_optionalFlippedBoard; }; - inline SendWorkOrderInfoData ToCpp(const HermesSendWorkOrderInfoData& data) + inline TransportFinishedData ToCpp(const HermesTransportFinishedData& data) { - SendWorkOrderInfoData result; - CToCpp(data.m_optionalQueryId, result.m_optionalQueryId); - CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); - CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); - CToCpp(data.m_optionalBoardIdCreatedBy, result.m_optionalBoardIdCreatedBy); - CToCpp(data.m_pOptionalFailedBoard, result.m_optionalFailedBoard); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); - CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); - CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_optionalBottomClearanceHeightInMM); - CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); - CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); - CToCpp(data.m_optionalSubBoards, result.m_optionalSubBoards); + TransportFinishedData result; + CToCpp(data.m_transferState, result.m_transferState); + CToCpp(data.m_boardId, result.m_boardId); return result; } - // ReplyWorkOrderInfoData + // QueryBoardInfoData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - Converter2C(const ReplyWorkOrderInfoData& data) + explicit Converter2C(const QueryBoardInfoData& data) { - CppToC(data.m_workOrderId, m_data.m_workOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); - CppToC(data.m_status, m_data.m_status); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); } }; - inline ReplyWorkOrderInfoData ToCpp(const HermesReplyWorkOrderInfoData& data) + inline QueryBoardInfoData ToCpp(const HermesQueryBoardInfoData& data) { - ReplyWorkOrderInfoData result; - CToCpp(data.m_workOrderId, result.m_workOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); - CToCpp(data.m_status, result.m_status); + QueryBoardInfoData result; + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); return result; } @@ -1083,7 +667,7 @@ namespace Hermes template<> struct Converter2C : ConverterBase { - Converter2C(const CommandData& data) + explicit Converter2C(const CommandData& data) { CppToC(data.m_command, m_data.m_command); } @@ -1095,172 +679,31 @@ namespace Hermes return result; } - - // QueryHermesCapabilitiesData - template<> - struct Converter2C : ConverterBase - { - Converter2C(const QueryHermesCapabilitiesData&) - { - } - }; - inline QueryHermesCapabilitiesData ToCpp(const HermesQueryHermesCapabilitiesData&) - { - QueryHermesCapabilitiesData result; - return result; - } - - // OptionalMessages - inline void CppToC(const MessageCheckAliveResponse&, HermesMessageCheckAliveResponse&) {} - inline void CppToC(const MessageBoardForecast&, HermesMessageBoardForecast&) {} - inline void CppToC(const MessageQueryBoardInfo&, HermesMessageQueryBoardInfo&) {} - inline void CppToC(const MessageSendBoardInfo&, HermesMessageSendBoardInfo&) {} - inline void CppToC(const MessageBoardArrived&, HermesMessageBoardArrived&) {} - inline void CppToC(const MessageBoardDeparted&, HermesMessageBoardDeparted&) {} - inline void CppToC(const MessageQueryWorkOrderInfo&, HermesMessageQueryWorkOrderInfo&) {} - inline void CppToC(const MessageReplyWorkOrderInfo&, HermesMessageReplyWorkOrderInfo&) {} - inline void CppToC(const MessageCommand&, HermesMessageCommand&) {} - - inline void CToCpp(const HermesMessageCheckAliveResponse&, MessageCheckAliveResponse&) {} - inline void CToCpp(const HermesMessageBoardForecast&, MessageBoardForecast&) {} - inline void CToCpp(const HermesMessageQueryBoardInfo&, MessageQueryBoardInfo&) {} - inline void CToCpp(const HermesMessageSendBoardInfo&, MessageSendBoardInfo&) {} - inline void CToCpp(const HermesMessageBoardArrived&, MessageBoardArrived&) {} - inline void CToCpp(const HermesMessageBoardDeparted&, MessageBoardDeparted&) {} - inline void CToCpp(const HermesMessageQueryWorkOrderInfo&, MessageQueryWorkOrderInfo&) {} - inline void CToCpp(const HermesMessageReplyWorkOrderInfo&, MessageReplyWorkOrderInfo&) {} - inline void CToCpp(const HermesMessageCommand&, MessageCommand&) {} - - // OptionalMessages - struct OptionalMessagesHolder : ConverterBase - { - HermesMessageCheckAliveResponse m_optionalMessageCheckAliveResponse; - HermesMessageBoardForecast m_optionalMessageBoardForecast; - HermesMessageQueryBoardInfo m_optionalMessageQueryBoardInfo; - HermesMessageSendBoardInfo m_optionalMessageSendBoardInfo; - HermesMessageBoardArrived m_optionalMessageBoardArrived; - HermesMessageBoardDeparted m_optionalMessageBoardDeparted; - HermesMessageQueryWorkOrderInfo m_optionalMessageQueryWorkOrderInfo; - HermesMessageReplyWorkOrderInfo m_optionalMessageReplyWorkOrderInfo; - HermesMessageCommand m_optionalMessageCommand; - }; - - inline void CppToC(const OptionalMessages& data, OptionalMessagesHolder& result) - { - auto& hermesData = result.m_data; - CppToC(data.m_optionalMessageCheckAliveResponse, result.m_optionalMessageCheckAliveResponse, hermesData.m_pOptionalMessageCheckAliveResponse); - CppToC(data.m_optionalMessageBoardForecast, result.m_optionalMessageBoardForecast, hermesData.m_pOptionalMessageBoardForecast); - CppToC(data.m_optionalMessageQueryBoardInfo, result.m_optionalMessageQueryBoardInfo, hermesData.m_pOptionalMessageQueryBoardInfo); - CppToC(data.m_optionalMessageSendBoardInfo, result.m_optionalMessageSendBoardInfo, hermesData.m_pOptionalMessageSendBoardInfo); - CppToC(data.m_optionalMessageBoardArrived, result.m_optionalMessageBoardArrived, hermesData.m_pOptionalMessageBoardArrived); - CppToC(data.m_optionalMessageBoardDeparted, result.m_optionalMessageBoardDeparted, hermesData.m_pOptionalMessageBoardDeparted); - CppToC(data.m_optionalMessageQueryWorkOrderInfo, result.m_optionalMessageQueryWorkOrderInfo, hermesData.m_pOptionalMessageQueryWorkOrderInfo); - CppToC(data.m_optionalMessageReplyWorkOrderInfo, result.m_optionalMessageReplyWorkOrderInfo, hermesData.m_pOptionalMessageReplyWorkOrderInfo); - CppToC(data.m_optionalMessageCommand, result.m_optionalMessageCommand, hermesData.m_pOptionalMessageCommand); - } - - inline void CToCpp(const HermesOptionalMessages& data, OptionalMessages& result) - { - CToCpp(data.m_pOptionalMessageCheckAliveResponse, result.m_optionalMessageCheckAliveResponse); - CToCpp(data.m_pOptionalMessageBoardForecast, result.m_optionalMessageBoardForecast); - CToCpp(data.m_pOptionalMessageQueryBoardInfo, result.m_optionalMessageQueryBoardInfo); - CToCpp(data.m_pOptionalMessageSendBoardInfo, result.m_optionalMessageSendBoardInfo); - CToCpp(data.m_pOptionalMessageBoardArrived, result.m_optionalMessageBoardArrived); - CToCpp(data.m_pOptionalMessageBoardDeparted, result.m_optionalMessageBoardDeparted); - CToCpp(data.m_pOptionalMessageQueryWorkOrderInfo, result.m_optionalMessageQueryWorkOrderInfo); - CToCpp(data.m_pOptionalMessageReplyWorkOrderInfo, result.m_optionalMessageReplyWorkOrderInfo); - CToCpp(data.m_pOptionalMessageCommand, result.m_optionalMessageCommand); - } - - // Attributes - inline void CppToC(const Attributes& data, HermesAttributes& result) - { - CppToC(data.m_productTypeId, result.m_productTypeId); - CppToC(data.m_topBarcode, result.m_topBarcode); - CppToC(data.m_bottomBarcode, result.m_bottomBarcode); - CppToC(data.m_length, result.m_length); - CppToC(data.m_width, result.m_width); - CppToC(data.m_thickness, result.m_thickness); - CppToC(data.m_conveyorSpeed, result.m_conveyorSpeed); - CppToC(data.m_topClearanceHeight, result.m_topClearanceHeight); - CppToC(data.m_bottomClearanceHeight, result.m_bottomClearanceHeight); - CppToC(data.m_weight, result.m_weight); - CppToC(data.m_workOrderId, result.m_workOrderId); - CppToC(data.m_batchId, result.m_batchId); - CppToC(data.m_route, result.m_route); - CppToC(data.m_action, result.m_action); - CppToC(data.m_subBoards, result.m_subBoards); - } - - inline void CToCpp(const HermesAttributes& data, Attributes& result) - { - CToCpp(data.m_productTypeId, result.m_productTypeId); - CToCpp(data.m_topBarcode, result.m_topBarcode); - CToCpp(data.m_bottomBarcode, result.m_bottomBarcode); - CToCpp(data.m_length, result.m_length); - CToCpp(data.m_width, result.m_width); - CToCpp(data.m_thickness, result.m_thickness); - CToCpp(data.m_conveyorSpeed, result.m_conveyorSpeed); - CToCpp(data.m_topClearanceHeight, result.m_topClearanceHeight); - CToCpp(data.m_bottomClearanceHeight, result.m_bottomClearanceHeight); - CToCpp(data.m_weight, result.m_weight); - CToCpp(data.m_workOrderId, result.m_workOrderId); - CToCpp(data.m_batchId, result.m_batchId); - CToCpp(data.m_route, result.m_route); - CToCpp(data.m_action, result.m_action); - CToCpp(data.m_subBoards, result.m_subBoards); - } - - // SendHermesCapabilitiesData - template<> - struct Converter2C : ConverterBase - { - Converter2C(const SendHermesCapabilitiesData& data) - { - CppToC(data.m_optionalMessages, m_optionalMessages); - m_data.m_pOptionalMessages = &m_optionalMessages.m_data; - CppToC(data.m_attributes, m_hermesAttributes); - m_data.m_pAttributes = &m_hermesAttributes; - } - private: - OptionalMessagesHolder m_optionalMessages; - HermesAttributes m_hermesAttributes; - }; - - inline SendHermesCapabilitiesData ToCpp(const HermesSendHermesCapabilitiesData& data) - { - SendHermesCapabilitiesData result; - CToCpp(data.m_pOptionalMessages, result.m_optionalMessages); - CToCpp(data.m_pAttributes, result.m_attributes); - return result; - } - - // UpstreamSettings template<> struct Converter2C : ConverterBase { - Converter2C(const UpstreamSettings& data) + explicit Converter2C(const UpstreamSettings& data) { - CppToC(data.m_machineId, m_data.m_machineId); - CppToC(data.m_hostAddress, m_data.m_hostAddress); - CppToC(data.m_port, m_data.m_port); - CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); - CppToC(data.m_reconnectWaitTimeInSeconds, m_data.m_reconnectWaitTimeInSeconds); - CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); - CppToC(data.m_checkState, m_data.m_checkState); + CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_hostAddress, m_data.m_hostAddress); + CppToC(data.m_port, m_data.m_port); + CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); + CppToC(data.m_reconnectWaitTimeInSeconds, m_data.m_reconnectWaitTimeInSeconds); + CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); + CppToC(data.m_checkState, m_data.m_checkState); } }; inline UpstreamSettings ToCpp(const HermesUpstreamSettings& data) { UpstreamSettings result; - CToCpp(data.m_machineId, result.m_machineId); - CToCpp(data.m_hostAddress, result.m_hostAddress); - CToCpp(data.m_port, result.m_port); - CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); + CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_hostAddress, result.m_hostAddress); + CToCpp(data.m_port, result.m_port); + CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); CToCpp(data.m_reconnectWaitTimeInSeconds, result.m_reconnectWaitTimeInSeconds); - CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); - CToCpp(data.m_checkState, result.m_checkState); + CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); + CToCpp(data.m_checkState, result.m_checkState); return result; } @@ -1268,27 +711,27 @@ namespace Hermes template<> struct Converter2C : ConverterBase { - Converter2C(const DownstreamSettings& data) + explicit Converter2C(const DownstreamSettings& data) { - CppToC(data.m_machineId, m_data.m_machineId); - CppToC(data.m_optionalClientAddress, m_data.m_optionalClientAddress); - CppToC(data.m_port, m_data.m_port); - CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); + CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_optionalClientAddress, m_data.m_optionalClientAddress); + CppToC(data.m_port, m_data.m_port); + CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); CppToC(data.m_reconnectWaitTimeInSeconds, m_data.m_reconnectWaitTimeInSeconds); - CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); - CppToC(data.m_checkState, m_data.m_checkState); + CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); + CppToC(data.m_checkState, m_data.m_checkState); } }; inline DownstreamSettings ToCpp(const HermesDownstreamSettings& data) { DownstreamSettings result; - CToCpp(data.m_machineId, result.m_machineId); - CToCpp(data.m_optionalClientAddress, result.m_optionalClientAddress); - CToCpp(data.m_port, result.m_port); - CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); + CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_optionalClientAddress, result.m_optionalClientAddress); + CToCpp(data.m_port, result.m_port); + CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); CToCpp(data.m_reconnectWaitTimeInSeconds, result.m_reconnectWaitTimeInSeconds); - CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); - CToCpp(data.m_checkState, result.m_checkState); + CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); + CToCpp(data.m_checkState, result.m_checkState); return result; } @@ -1296,16 +739,16 @@ namespace Hermes template<> struct Converter2C : ConverterBase { - Converter2C(const ConfigurationServiceSettings& data) + explicit Converter2C(const ConfigurationServiceSettings& data) { - CppToC(data.m_port, m_data.m_port); + CppToC(data.m_port, m_data.m_port); CppToC(data.m_reconnectWaitTimeInSeconds, m_data.m_reconnectWaitTimeInSeconds); } }; inline ConfigurationServiceSettings ToCpp(const HermesConfigurationServiceSettings& data) { ConfigurationServiceSettings result; - CToCpp(data.m_port, result.m_port); + CToCpp(data.m_port, result.m_port); CToCpp(data.m_reconnectWaitTimeInSeconds, result.m_reconnectWaitTimeInSeconds); return result; } @@ -1314,23 +757,23 @@ namespace Hermes template<> struct Converter2C : ConverterBase { - Converter2C(const VerticalServiceSettings& data) + explicit Converter2C(const VerticalServiceSettings& data) { - CppToC(data.m_systemId, m_data.m_systemId); - CppToC(data.m_port, m_data.m_port); + CppToC(data.m_systemId, m_data.m_systemId); + CppToC(data.m_port, m_data.m_port); CppToC(data.m_reconnectWaitTimeInSeconds, m_data.m_reconnectWaitTimeInSeconds); - CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); - CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); + CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); + CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); } }; inline VerticalServiceSettings ToCpp(const HermesVerticalServiceSettings& data) { VerticalServiceSettings result; - CToCpp(data.m_systemId, result.m_systemId); - CToCpp(data.m_port, result.m_port); + CToCpp(data.m_systemId, result.m_systemId); + CToCpp(data.m_port, result.m_port); CToCpp(data.m_reconnectWaitTimeInSeconds, result.m_reconnectWaitTimeInSeconds); - CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); - CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); + CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); + CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); return result; } @@ -1338,45 +781,26 @@ namespace Hermes template<> struct Converter2C : ConverterBase { - Converter2C(const VerticalClientSettings& data) + explicit Converter2C(const VerticalClientSettings& data) { - CppToC(data.m_systemId, m_data.m_systemId); - CppToC(data.m_hostAddress, m_data.m_hostAddress); - CppToC(data.m_port, m_data.m_port); + CppToC(data.m_systemId, m_data.m_systemId); + CppToC(data.m_hostAddress, m_data.m_hostAddress); + CppToC(data.m_port, m_data.m_port); CppToC(data.m_reconnectWaitTimeInSeconds, m_data.m_reconnectWaitTimeInSeconds); - CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); - CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); + CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); + CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); } }; inline VerticalClientSettings ToCpp(const HermesVerticalClientSettings& data) { VerticalClientSettings result; - CToCpp(data.m_systemId, result.m_systemId); - CToCpp(data.m_hostAddress, result.m_hostAddress); - CToCpp(data.m_port, result.m_port); + CToCpp(data.m_systemId, result.m_systemId); + CToCpp(data.m_hostAddress, result.m_hostAddress); + CToCpp(data.m_port, result.m_port); CToCpp(data.m_reconnectWaitTimeInSeconds, result.m_reconnectWaitTimeInSeconds); - CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); - CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); + CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); + CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); return result; } - // Error - template<> - struct Converter2C : ConverterBase - { - Converter2C(const Error& data) - { - CppToC(data.m_code, m_data.m_code); - CppToC(data.m_text, m_data.m_text); - } - }; - inline Error ToCpp(const HermesError& data) - { - Error result; - CToCpp(data.m_code, result.m_code); - CToCpp(data.m_text, result.m_text); - return result; - } - - -} +} // namespace Hermes diff --git a/src/include/HermesModern.hpp b/src/include/HermesModern.hpp index a2e42a9..6946384 100644 --- a/src/include/HermesModern.hpp +++ b/src/include/HermesModern.hpp @@ -1,7 +1,57 @@ -// ============================================================================== +// ============================================================================= // src/include/HermesModern.hpp -// Modern C++ Wrapper for the Hermes Standard Library (Complete Suite) -// ============================================================================== +// Modern C++ wrapper for the Hermes Standard library. +// +// Wraps Hermes::Downstream and Hermes::Upstream behind std::function callbacks +// so callers register lambdas instead of implementing virtual interfaces. +// +// FIXES applied vs original: +// +// Modern::Downstream::InternalCallbackWrapper +// - Inherits Hermes::IDownstreamCallback (correct, was correct) +// - WRONG overrides removed: +// On(EState, BoardAvailableData) -> belongs to IUpstreamCallback +// On(EState, RevokeBoardAvailableData) -> belongs to IUpstreamCallback +// On(EState, TransportFinishedData) -> belongs to IUpstreamCallback +// On(EState, BoardForecastData) -> belongs to IUpstreamCallback +// On(EState, SendBoardInfoData) -> belongs to IUpstreamCallback +// - MISSING overrides added (were pure virtual, caused link failure): +// On(EState, MachineReadyData) = 0 in IDownstreamCallback +// On(EState, RevokeMachineReadyData) = 0 in IDownstreamCallback +// On(EState, StartTransportData) = 0 in IDownstreamCallback +// On(EState, StopTransportData) = 0 in IDownstreamCallback +// - WRONG callback types removed from public API: +// BoardAvailableCallback, RevokeBoardAvailableCallback, +// TransportFinishedCallback, BoardForecastCallback, SendBoardInfoCallback +// - CORRECT callback types added to public API: +// MachineReadyCallback, RevokeMachineReadyCallback, +// StartTransportCallback, StopTransportCallback, QueryBoardInfoCallback +// +// Modern::Upstream::InternalCallbackWrapper +// - Inherits Hermes::IUpstreamCallback (correct, was correct) +// - WRONG overrides removed: +// On(EState, MachineReadyData) -> belongs to IDownstreamCallback +// On(EState, RevokeMachineReadyData) -> belongs to IDownstreamCallback +// On(EState, StartTransportData) -> belongs to IDownstreamCallback +// On(EState, StopTransportData) -> belongs to IDownstreamCallback +// On(EState, QueryBoardInfoData) -> belongs to IDownstreamCallback +// - MISSING overrides added (were pure virtual): +// On(EState, BoardAvailableData) = 0 in IUpstreamCallback +// On(EState, RevokeBoardAvailableData) = 0 in IUpstreamCallback +// On(EState, TransportFinishedData) = 0 in IUpstreamCallback +// - WRONG callback types removed from public API: +// MachineReadyCallback, RevokeMachineReadyCallback, +// StartTransportCallback, StopTransportCallback, QueryBoardInfoCallback +// - CORRECT callback types added to public API: +// BoardAvailableCallback, RevokeBoardAvailableCallback, +// TransportFinishedCallback, BoardForecastCallback, SendBoardInfoCallback +// +// Both classes: +// - Stop() is now safe to call multiple times (guarded by exchange) +// - Enable() cannot be called twice without Stop() in between +// - m_laneId stored but unused warning removed (used in construction) +// ============================================================================= + #pragma once #include "Hermes.hpp" @@ -13,510 +63,409 @@ namespace Hermes { namespace Modern { -// ============================================================================== -// Downstream Wrapper (Receiver - Horizontal) -// ============================================================================== -class Downstream { +// ============================================================================= +// Modern::Downstream +// +// Listens on a TCP port for an incoming Upstream machine connection. +// Receives messages that the Upstream machine sends: +// ServiceDescription, MachineReady, RevokeMachineReady, +// StartTransport, StopTransport, QueryBoardInfo +// +// Sends messages that the Downstream machine produces: +// ServiceDescription, BoardAvailable, RevokeBoardAvailable, +// TransportFinished, BoardForecast, SendBoardInfo, +// Notification, CheckAlive, Command +// ============================================================================= +class Downstream +{ public: - using ConnectedCallback = std::function; - using DisconnectedCallback = std::function; - using StateChangeCallback = std::function; - using TraceCallback = std::function; - - using ServiceDescriptionCallback = std::function; - using MachineReadyCallback = std::function; - using RevokeMachineReadyCallback = std::function; - using StartTransportCallback = std::function; - using StopTransportCallback = std::function; - using QueryBoardInfoCallback = std::function; - - using NotificationCallback = std::function; - using CheckAliveCallback = std::function; - using CommandCallback = std::function; - - Downstream(unsigned laneId) : m_laneId(laneId), m_isRunning(false) { + // --- Connection lifecycle callbacks --- + using ConnectedCallback = std::function; + using DisconnectedCallback = std::function; + using StateChangeCallback = std::function; + using TraceCallback = std::function; + + // --- Messages RECEIVED from the Upstream machine --- + using ServiceDescriptionCallback = std::function; + using MachineReadyCallback = std::function; + using RevokeMachineReadyCallback = std::function; + using StartTransportCallback = std::function; + using StopTransportCallback = std::function; + using QueryBoardInfoCallback = std::function; + + // --- Auxiliary messages (either direction) --- + using NotificationCallback = std::function; + using CheckAliveCallback = std::function; + using CommandCallback = std::function; + + explicit Downstream(unsigned laneId) + : m_isRunning(false) + { m_callbackWrapper = std::make_unique(this); - m_downstream = std::make_unique(laneId, *m_callbackWrapper); + m_downstream = std::make_unique(laneId, *m_callbackWrapper); } ~Downstream() { Stop(); } - void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } - void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } - void RegisterStateChangeCallback(StateChangeCallback cb) { m_onStateChange = cb; } - void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } - - void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb) { m_onServiceDescription = cb; } - void RegisterMachineReadyCallback(MachineReadyCallback cb) { m_onMachineReady = cb; } - void RegisterRevokeMachineReadyCallback(RevokeMachineReadyCallback cb) { m_onRevokeMachineReady = cb; } - void RegisterStartTransportCallback(StartTransportCallback cb) { m_onStartTransport = cb; } - void RegisterStopTransportCallback(StopTransportCallback cb) { m_onStopTransport = cb; } - void RegisterQueryBoardInfoCallback(QueryBoardInfoCallback cb) { m_onQueryBoardInfo = cb; } - void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } - void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } - void RegisterCommandCallback(CommandCallback cb) { m_onCommand = cb; } - - void Enable(const DownstreamSettings& settings) { - if (m_isRunning) return; + Downstream(const Downstream&) = delete; + Downstream& operator=(const Downstream&) = delete; + + // --- Register callbacks (call before Enable) --- + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = std::move(cb); } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = std::move(cb); } + void RegisterStateChangeCallback(StateChangeCallback cb) { m_onStateChange = std::move(cb); } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = std::move(cb); } + void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb){ m_onServiceDescription = std::move(cb); } + void RegisterMachineReadyCallback(MachineReadyCallback cb) { m_onMachineReady = std::move(cb); } + void RegisterRevokeMachineReadyCallback(RevokeMachineReadyCallback cb){ m_onRevokeMachineReady = std::move(cb); } + void RegisterStartTransportCallback(StartTransportCallback cb) { m_onStartTransport = std::move(cb); } + void RegisterStopTransportCallback(StopTransportCallback cb) { m_onStopTransport = std::move(cb); } + void RegisterQueryBoardInfoCallback(QueryBoardInfoCallback cb) { m_onQueryBoardInfo = std::move(cb); } + void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = std::move(cb); } + void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = std::move(cb); } + void RegisterCommandCallback(CommandCallback cb) { m_onCommand = std::move(cb); } + + // --- Lifecycle --- + + // Enable starts listening and launches the network thread. + // Call this after registering callbacks. + void Enable(const DownstreamSettings& settings) + { + if (m_isRunning.exchange(true)) + return; // already running + m_downstream->Enable(settings); - m_isRunning = true; m_networkThread = std::thread([this]() { m_downstream->Run(); }); } - void Stop() { - if (m_isRunning) { - m_downstream->Stop(); - if (m_networkThread.joinable()) m_networkThread.join(); - m_isRunning = false; - } + // Stop shuts down the connection and joins the network thread. + // Safe to call multiple times. + void Stop() + { + if (!m_isRunning.exchange(false)) + return; // already stopped + + m_downstream->Stop(); + if (m_networkThread.joinable()) + m_networkThread.join(); } + // --- Send messages TO the Upstream machine --- + // Must be called from within a Post() lambda or a callback to be thread-safe. template void Signal(unsigned sessionId, const T& data) { m_downstream->Signal(sessionId, data); } + // Post a callable onto the Hermes network thread (thread-safe). + template + void Post(F&& f) { m_downstream->Post(std::forward(f)); } + private: - class InternalCallbackWrapper : public Hermes::IDownstreamCallback { - Downstream* m_parent; - public: - InternalCallbackWrapper(Downstream* parent) : m_parent(parent) {} + // ------------------------------------------------------------------------- + // InternalCallbackWrapper + // Implements Hermes::IDownstreamCallback and forwards to std::function members. + // Pure virtuals from IDownstreamCallback that MUST be overridden: + // OnConnected, OnDisconnected, OnState, OnTrace + // On(EState, ServiceDescriptionData) + // On(EState, MachineReadyData) + // On(EState, RevokeMachineReadyData) + // On(EState, StartTransportData) + // On(EState, StopTransportData) + // On(NotificationData) + // On(CommandData) + // ------------------------------------------------------------------------- + struct InternalCallbackWrapper : Hermes::IDownstreamCallback + { + explicit InternalCallbackWrapper(Downstream* parent) : m_parent(parent) {} - void OnConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { - if (m_parent->m_onConnected) m_parent->m_onConnected(info); + void OnConnected(unsigned sessionId, EState state, const ConnectionInfo& info) override + { + if (m_parent->m_onConnected) m_parent->m_onConnected(sessionId, info); } - void OnDisconnected(unsigned sessionId, Hermes::EState state, const Hermes::Error& error) override { - if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error); + + void OnDisconnected(unsigned sessionId, EState, const Error& error) override + { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(sessionId, error); } - void OnState(unsigned sessionId, Hermes::EState state) override { - if (m_parent->m_onStateChange) m_parent->m_onStateChange(state); + + void OnState(unsigned sessionId, EState state) override + { + if (m_parent->m_onStateChange) m_parent->m_onStateChange(sessionId, state); } - void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { - if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); + + void OnTrace(unsigned sessionId, ETraceType type, StringView trace) override + { + if (m_parent->m_onTrace) m_parent->m_onTrace(sessionId, type, trace); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::ServiceDescriptionData& data) override { - if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(data); + // Messages received FROM the Upstream machine: + void On(unsigned sessionId, EState state, const ServiceDescriptionData& data) override + { + if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(sessionId, state, data); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::MachineReadyData& data) override { - if (m_parent->m_onMachineReady) m_parent->m_onMachineReady(data); + + void On(unsigned sessionId, EState state, const MachineReadyData& data) override + { + if (m_parent->m_onMachineReady) m_parent->m_onMachineReady(sessionId, state, data); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::RevokeMachineReadyData& data) override { - if (m_parent->m_onRevokeMachineReady) m_parent->m_onRevokeMachineReady(data); + + void On(unsigned sessionId, EState state, const RevokeMachineReadyData& data) override + { + if (m_parent->m_onRevokeMachineReady) m_parent->m_onRevokeMachineReady(sessionId, state, data); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::StartTransportData& data) override { - if (m_parent->m_onStartTransport) m_parent->m_onStartTransport(data); + + void On(unsigned sessionId, EState state, const StartTransportData& data) override + { + if (m_parent->m_onStartTransport) m_parent->m_onStartTransport(sessionId, state, data); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::StopTransportData& data) override { - if (m_parent->m_onStopTransport) m_parent->m_onStopTransport(data); + + void On(unsigned sessionId, EState state, const StopTransportData& data) override + { + if (m_parent->m_onStopTransport) m_parent->m_onStopTransport(sessionId, state, data); } - void On(unsigned sessionId, const Hermes::QueryBoardInfoData& data) override { - if (m_parent->m_onQueryBoardInfo) m_parent->m_onQueryBoardInfo(data); + + void On(unsigned sessionId, const QueryBoardInfoData& data) override + { + if (m_parent->m_onQueryBoardInfo) m_parent->m_onQueryBoardInfo(sessionId, data); } - void On(unsigned sessionId, const Hermes::NotificationData& data) override { - if (m_parent->m_onNotification) m_parent->m_onNotification(data); + + // Auxiliary messages: + void On(unsigned sessionId, const NotificationData& data) override + { + if (m_parent->m_onNotification) m_parent->m_onNotification(sessionId, data); } - void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { - if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); + + void On(unsigned sessionId, const CheckAliveData& data) override + { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(sessionId, data); } - void On(unsigned sessionId, const Hermes::CommandData& data) override { - if (m_parent->m_onCommand) m_parent->m_onCommand(data); + + void On(unsigned sessionId, const CommandData& data) override + { + if (m_parent->m_onCommand) m_parent->m_onCommand(sessionId, data); } + + Downstream* m_parent; }; - unsigned m_laneId; - std::atomic m_isRunning; - std::thread m_networkThread; + std::atomic m_isRunning; + std::thread m_networkThread; std::unique_ptr m_callbackWrapper; - std::unique_ptr m_downstream; - - ConnectedCallback m_onConnected; - DisconnectedCallback m_onDisconnected; - StateChangeCallback m_onStateChange; - TraceCallback m_onTrace; - - ServiceDescriptionCallback m_onServiceDescription; - MachineReadyCallback m_onMachineReady; - RevokeMachineReadyCallback m_onRevokeMachineReady; - StartTransportCallback m_onStartTransport; - StopTransportCallback m_onStopTransport; - QueryBoardInfoCallback m_onQueryBoardInfo; - - NotificationCallback m_onNotification; - CheckAliveCallback m_onCheckAlive; - CommandCallback m_onCommand; + std::unique_ptr m_downstream; + + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + StateChangeCallback m_onStateChange; + TraceCallback m_onTrace; + ServiceDescriptionCallback m_onServiceDescription; + MachineReadyCallback m_onMachineReady; + RevokeMachineReadyCallback m_onRevokeMachineReady; + StartTransportCallback m_onStartTransport; + StopTransportCallback m_onStopTransport; + QueryBoardInfoCallback m_onQueryBoardInfo; + NotificationCallback m_onNotification; + CheckAliveCallback m_onCheckAlive; + CommandCallback m_onCommand; }; -// ============================================================================== -// Upstream Wrapper (Sender - Horizontal) -// ============================================================================== -class Upstream { +// ============================================================================= +// Modern::Upstream +// +// Connects to a Downstream machine's TCP port. +// Receives messages that the Downstream machine sends: +// ServiceDescription, BoardAvailable, RevokeBoardAvailable, +// TransportFinished, BoardForecast, SendBoardInfo +// +// Sends messages that the Upstream machine produces: +// ServiceDescription, MachineReady, RevokeMachineReady, +// StartTransport, StopTransport, QueryBoardInfo, +// Notification, CheckAlive, Command +// ============================================================================= +class Upstream +{ public: - using ConnectedCallback = std::function; - using DisconnectedCallback = std::function; - using StateChangeCallback = std::function; - using TraceCallback = std::function; - - using ServiceDescriptionCallback = std::function; - using BoardAvailableCallback = std::function; - using RevokeBoardAvailableCallback = std::function; - using TransportFinishedCallback = std::function; - using BoardForecastCallback = std::function; - using SendBoardInfoCallback = std::function; - - using NotificationCallback = std::function; - using CheckAliveCallback = std::function; - using CommandCallback = std::function; - - Upstream(unsigned laneId) : m_laneId(laneId), m_isRunning(false) { + // --- Connection lifecycle callbacks --- + using ConnectedCallback = std::function; + using DisconnectedCallback = std::function; + using StateChangeCallback = std::function; + using TraceCallback = std::function; + + // --- Messages RECEIVED from the Downstream machine --- + using ServiceDescriptionCallback = std::function; + using BoardAvailableCallback = std::function; + using RevokeBoardAvailableCallback = std::function; + using TransportFinishedCallback = std::function; + using BoardForecastCallback = std::function; + using SendBoardInfoCallback = std::function; + + // --- Auxiliary messages (either direction) --- + using NotificationCallback = std::function; + using CheckAliveCallback = std::function; + using CommandCallback = std::function; + + explicit Upstream(unsigned laneId) + : m_isRunning(false) + { m_callbackWrapper = std::make_unique(this); - m_upstream = std::make_unique(laneId, *m_callbackWrapper); + m_upstream = std::make_unique(laneId, *m_callbackWrapper); } ~Upstream() { Stop(); } - void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } - void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } - void RegisterStateChangeCallback(StateChangeCallback cb) { m_onStateChange = cb; } - void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } - - void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb) { m_onServiceDescription = cb; } - void RegisterBoardAvailableCallback(BoardAvailableCallback cb) { m_onBoardAvailable = cb; } - void RegisterRevokeBoardAvailableCallback(RevokeBoardAvailableCallback cb) { m_onRevokeBoardAvailable = cb; } - void RegisterTransportFinishedCallback(TransportFinishedCallback cb) { m_onTransportFinished = cb; } - void RegisterBoardForecastCallback(BoardForecastCallback cb) { m_onBoardForecast = cb; } - void RegisterSendBoardInfoCallback(SendBoardInfoCallback cb) { m_onSendBoardInfo = cb; } - void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } - void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } - void RegisterCommandCallback(CommandCallback cb) { m_onCommand = cb; } - - void Enable(const UpstreamSettings& settings) { - if (m_isRunning) return; + Upstream(const Upstream&) = delete; + Upstream& operator=(const Upstream&) = delete; + + // --- Register callbacks (call before Enable) --- + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = std::move(cb); } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = std::move(cb); } + void RegisterStateChangeCallback(StateChangeCallback cb) { m_onStateChange = std::move(cb); } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = std::move(cb); } + void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb) { m_onServiceDescription = std::move(cb); } + void RegisterBoardAvailableCallback(BoardAvailableCallback cb) { m_onBoardAvailable = std::move(cb); } + void RegisterRevokeBoardAvailableCallback(RevokeBoardAvailableCallback cb){ m_onRevokeBoardAvailable = std::move(cb); } + void RegisterTransportFinishedCallback(TransportFinishedCallback cb) { m_onTransportFinished = std::move(cb); } + void RegisterBoardForecastCallback(BoardForecastCallback cb) { m_onBoardForecast = std::move(cb); } + void RegisterSendBoardInfoCallback(SendBoardInfoCallback cb) { m_onSendBoardInfo = std::move(cb); } + void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = std::move(cb); } + void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = std::move(cb); } + void RegisterCommandCallback(CommandCallback cb) { m_onCommand = std::move(cb); } + + // --- Lifecycle --- + + // Enable connects to the downstream host and launches the network thread. + void Enable(const UpstreamSettings& settings) + { + if (m_isRunning.exchange(true)) + return; + m_upstream->Enable(settings); - m_isRunning = true; m_networkThread = std::thread([this]() { m_upstream->Run(); }); } - void Stop() { - if (m_isRunning) { - m_upstream->Stop(); - if (m_networkThread.joinable()) m_networkThread.join(); - m_isRunning = false; - } + // Stop disconnects and joins the network thread. Safe to call multiple times. + void Stop() + { + if (!m_isRunning.exchange(false)) + return; + + m_upstream->Stop(); + if (m_networkThread.joinable()) + m_networkThread.join(); } + // --- Send messages TO the Downstream machine --- template void Signal(unsigned sessionId, const T& data) { m_upstream->Signal(sessionId, data); } + // Post a callable onto the Hermes network thread (thread-safe). + template + void Post(F&& f) { m_upstream->Post(std::forward(f)); } + private: - class InternalCallbackWrapper : public Hermes::IUpstreamCallback { - Upstream* m_parent; - public: - InternalCallbackWrapper(Upstream* parent) : m_parent(parent) {} + // ------------------------------------------------------------------------- + // InternalCallbackWrapper + // Implements Hermes::IUpstreamCallback and forwards to std::function members. + // Pure virtuals from IUpstreamCallback that MUST be overridden: + // OnConnected, OnDisconnected, OnState, OnTrace + // On(EState, ServiceDescriptionData) + // On(EState, BoardAvailableData) + // On(EState, RevokeBoardAvailableData) + // On(EState, TransportFinishedData) + // On(NotificationData) + // On(CommandData) + // ------------------------------------------------------------------------- + struct InternalCallbackWrapper : Hermes::IUpstreamCallback + { + explicit InternalCallbackWrapper(Upstream* parent) : m_parent(parent) {} - void OnConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { - if (m_parent->m_onConnected) m_parent->m_onConnected(info); - } - void OnDisconnected(unsigned sessionId, Hermes::EState state, const Hermes::Error& error) override { - if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error); - } - void OnState(unsigned sessionId, Hermes::EState state) override { - if (m_parent->m_onStateChange) m_parent->m_onStateChange(state); - } - void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { - if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); + void OnConnected(unsigned sessionId, EState state, const ConnectionInfo& info) override + { + if (m_parent->m_onConnected) m_parent->m_onConnected(sessionId, info); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::ServiceDescriptionData& data) override { - if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(data); - } - void On(unsigned sessionId, Hermes::EState state, const Hermes::BoardAvailableData& data) override { - if (m_parent->m_onBoardAvailable) m_parent->m_onBoardAvailable(data); - } - void On(unsigned sessionId, Hermes::EState state, const Hermes::RevokeBoardAvailableData& data) override { - if (m_parent->m_onRevokeBoardAvailable) m_parent->m_onRevokeBoardAvailable(data); - } - void On(unsigned sessionId, Hermes::EState state, const Hermes::TransportFinishedData& data) override { - if (m_parent->m_onTransportFinished) m_parent->m_onTransportFinished(data); - } - void On(unsigned sessionId, Hermes::EState state, const Hermes::BoardForecastData& data) override { - if (m_parent->m_onBoardForecast) m_parent->m_onBoardForecast(data); - } - void On(unsigned sessionId, const Hermes::SendBoardInfoData& data) override { - if (m_parent->m_onSendBoardInfo) m_parent->m_onSendBoardInfo(data); - } - void On(unsigned sessionId, const Hermes::NotificationData& data) override { - if (m_parent->m_onNotification) m_parent->m_onNotification(data); - } - void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { - if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); + void OnDisconnected(unsigned sessionId, EState, const Error& error) override + { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(sessionId, error); } - void On(unsigned sessionId, const Hermes::CommandData& data) override { - if (m_parent->m_onCommand) m_parent->m_onCommand(data); - } - }; - - unsigned m_laneId; - std::atomic m_isRunning; - std::thread m_networkThread; - std::unique_ptr m_callbackWrapper; - std::unique_ptr m_upstream; - - ConnectedCallback m_onConnected; - DisconnectedCallback m_onDisconnected; - StateChangeCallback m_onStateChange; - TraceCallback m_onTrace; - - ServiceDescriptionCallback m_onServiceDescription; - BoardAvailableCallback m_onBoardAvailable; - RevokeBoardAvailableCallback m_onRevokeBoardAvailable; - TransportFinishedCallback m_onTransportFinished; - BoardForecastCallback m_onBoardForecast; - SendBoardInfoCallback m_onSendBoardInfo; - - NotificationCallback m_onNotification; - CheckAliveCallback m_onCheckAlive; - CommandCallback m_onCommand; -}; - - -// ============================================================================== -// VerticalService Wrapper (Machine Server - Vertical) -// ============================================================================== -class VerticalService { -public: - using ConnectedCallback = std::function; - using DisconnectedCallback = std::function; - using TraceCallback = std::function; - - using SupervisoryServiceDescriptionCallback = std::function; - using GetConfigurationCallback = std::function; - using SetConfigurationCallback = std::function; - using SendWorkOrderInfoCallback = std::function; - using QueryHermesCapabilitiesCallback = std::function; - - using NotificationCallback = std::function; - using CheckAliveCallback = std::function; - - VerticalService() : m_isRunning(false) { - m_callbackWrapper = std::make_unique(this); - m_verticalService = std::make_unique(*m_callbackWrapper); - } - ~VerticalService() { Stop(); } - - void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } - void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } - void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } - - void RegisterSupervisoryServiceDescriptionCallback(SupervisoryServiceDescriptionCallback cb) { m_onSupervisoryServiceDescription = cb; } - void RegisterGetConfigurationCallback(GetConfigurationCallback cb) { m_onGetConfiguration = cb; } - void RegisterSetConfigurationCallback(SetConfigurationCallback cb) { m_onSetConfiguration = cb; } - void RegisterSendWorkOrderInfoCallback(SendWorkOrderInfoCallback cb) { m_onSendWorkOrderInfo = cb; } - void RegisterQueryHermesCapabilitiesCallback(QueryHermesCapabilitiesCallback cb) { m_onQueryHermesCapabilities = cb; } - void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } - void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } - - void Enable(const VerticalServiceSettings& settings) { - if (m_isRunning) return; - m_verticalService->Enable(settings); - m_isRunning = true; - m_networkThread = std::thread([this]() { m_verticalService->Run(); }); - } - - void Stop() { - if (m_isRunning) { - m_verticalService->Stop(); - if (m_networkThread.joinable()) m_networkThread.join(); - m_isRunning = false; + void OnState(unsigned sessionId, EState state) override + { + if (m_parent->m_onStateChange) m_parent->m_onStateChange(sessionId, state); } - } - - template - void Signal(unsigned sessionId, const T& data) { m_verticalService->Signal(sessionId, data); } - - template - void SignalBroadcast(const T& data) { m_verticalService->Signal(data); } -private: - class InternalCallbackWrapper : public Hermes::IVerticalServiceCallback { - VerticalService* m_parent; - public: - InternalCallbackWrapper(VerticalService* parent) : m_parent(parent) {} - - void OnConnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::ConnectionInfo& info) override { - if (m_parent->m_onConnected) m_parent->m_onConnected(info, state); - } - void OnDisconnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::Error& error) override { - if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error, state); - } - void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { - if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); + void OnTrace(unsigned sessionId, ETraceType type, StringView trace) override + { + if (m_parent->m_onTrace) m_parent->m_onTrace(sessionId, type, trace); } - void On(unsigned sessionId, Hermes::EVerticalState state, const Hermes::SupervisoryServiceDescriptionData& data) override { - if (m_parent->m_onSupervisoryServiceDescription) m_parent->m_onSupervisoryServiceDescription(data, state); - } - void On(unsigned sessionId, const Hermes::GetConfigurationData& data, const Hermes::ConnectionInfo& info) override { - if (m_parent->m_onGetConfiguration) m_parent->m_onGetConfiguration(data, info); - } - void On(unsigned sessionId, const Hermes::SetConfigurationData& data, const Hermes::ConnectionInfo& info) override { - if (m_parent->m_onSetConfiguration) m_parent->m_onSetConfiguration(data, info); + // Messages received FROM the Downstream machine: + void On(unsigned sessionId, EState state, const ServiceDescriptionData& data) override + { + if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(sessionId, state, data); } - void On(unsigned sessionId, const Hermes::SendWorkOrderInfoData& data) override { - if (m_parent->m_onSendWorkOrderInfo) m_parent->m_onSendWorkOrderInfo(data); - } - void On(unsigned sessionId, const Hermes::QueryHermesCapabilitiesData& data) override { - if (m_parent->m_onQueryHermesCapabilities) m_parent->m_onQueryHermesCapabilities(data); - } - void On(unsigned sessionId, const Hermes::NotificationData& data) override { - if (m_parent->m_onNotification) m_parent->m_onNotification(data); - } - void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { - if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); - } - }; - std::atomic m_isRunning; - std::thread m_networkThread; - std::unique_ptr m_callbackWrapper; - std::unique_ptr m_verticalService; - - ConnectedCallback m_onConnected; - DisconnectedCallback m_onDisconnected; - TraceCallback m_onTrace; - - SupervisoryServiceDescriptionCallback m_onSupervisoryServiceDescription; - GetConfigurationCallback m_onGetConfiguration; - SetConfigurationCallback m_onSetConfiguration; - SendWorkOrderInfoCallback m_onSendWorkOrderInfo; - QueryHermesCapabilitiesCallback m_onQueryHermesCapabilities; - NotificationCallback m_onNotification; - CheckAliveCallback m_onCheckAlive; -}; - - -// ============================================================================== -// VerticalClient Wrapper (MES/Factory Cloud Client - Vertical) -// ============================================================================== -class VerticalClient { -public: - using ConnectedCallback = std::function; - using DisconnectedCallback = std::function; - using TraceCallback = std::function; - - using SupervisoryServiceDescriptionCallback = std::function; - using BoardArrivedCallback = std::function; - using BoardDepartedCallback = std::function; - using QueryWorkOrderInfoCallback = std::function; - using ReplyWorkOrderInfoCallback = std::function; - using CurrentConfigurationCallback = std::function; - using SendHermesCapabilitiesCallback = std::function; - - using NotificationCallback = std::function; - using CheckAliveCallback = std::function; - - VerticalClient() : m_isRunning(false) { - m_callbackWrapper = std::make_unique(this); - m_verticalClient = std::make_unique(*m_callbackWrapper); - } - - ~VerticalClient() { Stop(); } - - void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } - void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } - void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } - - void RegisterSupervisoryServiceDescriptionCallback(SupervisoryServiceDescriptionCallback cb) { m_onSupervisoryServiceDescription = cb; } - void RegisterBoardArrivedCallback(BoardArrivedCallback cb) { m_onBoardArrived = cb; } - void RegisterBoardDepartedCallback(BoardDepartedCallback cb) { m_onBoardDeparted = cb; } - void RegisterQueryWorkOrderInfoCallback(QueryWorkOrderInfoCallback cb) { m_onQueryWorkOrderInfo = cb; } - void RegisterReplyWorkOrderInfoCallback(ReplyWorkOrderInfoCallback cb) { m_onReplyWorkOrderInfo = cb; } - void RegisterCurrentConfigurationCallback(CurrentConfigurationCallback cb) { m_onCurrentConfiguration = cb; } - void RegisterSendHermesCapabilitiesCallback(SendHermesCapabilitiesCallback cb) { m_onSendHermesCapabilities = cb; } - void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } - void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } - - void Enable(const VerticalClientSettings& settings) { - if (m_isRunning) return; - m_verticalClient->Enable(settings); - m_isRunning = true; - m_networkThread = std::thread([this]() { m_verticalClient->Run(); }); - } - - void Stop() { - if (m_isRunning) { - m_verticalClient->Stop(); - if (m_networkThread.joinable()) m_networkThread.join(); - m_isRunning = false; + void On(unsigned sessionId, EState state, const BoardAvailableData& data) override + { + if (m_parent->m_onBoardAvailable) m_parent->m_onBoardAvailable(sessionId, state, data); } - } - template - void Signal(unsigned sessionId, const T& data) { m_verticalClient->Signal(sessionId, data); } - -private: - class InternalCallbackWrapper : public Hermes::IVerticalClientCallback { - VerticalClient* m_parent; - public: - InternalCallbackWrapper(VerticalClient* parent) : m_parent(parent) {} - - void OnConnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::ConnectionInfo& info) override { - if (m_parent->m_onConnected) m_parent->m_onConnected(info, state); - } - void OnDisconnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::Error& error) override { - if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error, state); - } - void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { - if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); + void On(unsigned sessionId, EState state, const RevokeBoardAvailableData& data) override + { + if (m_parent->m_onRevokeBoardAvailable) m_parent->m_onRevokeBoardAvailable(sessionId, state, data); } - void On(unsigned sessionId, Hermes::EVerticalState state, const Hermes::SupervisoryServiceDescriptionData& data) override { - if (m_parent->m_onSupervisoryServiceDescription) m_parent->m_onSupervisoryServiceDescription(data, state); + void On(unsigned sessionId, EState state, const TransportFinishedData& data) override + { + if (m_parent->m_onTransportFinished) m_parent->m_onTransportFinished(sessionId, state, data); } - void On(unsigned sessionId, const Hermes::BoardArrivedData& data) override { - if (m_parent->m_onBoardArrived) m_parent->m_onBoardArrived(data); - } - void On(unsigned sessionId, const Hermes::BoardDepartedData& data) override { - if (m_parent->m_onBoardDeparted) m_parent->m_onBoardDeparted(data); - } - void On(unsigned sessionId, const Hermes::QueryWorkOrderInfoData& data) override { - if (m_parent->m_onQueryWorkOrderInfo) m_parent->m_onQueryWorkOrderInfo(data); - } - void On(unsigned sessionId, const Hermes::ReplyWorkOrderInfoData& data) override { - if (m_parent->m_onReplyWorkOrderInfo) m_parent->m_onReplyWorkOrderInfo(data); + + void On(unsigned sessionId, EState state, const BoardForecastData& data) override + { + if (m_parent->m_onBoardForecast) m_parent->m_onBoardForecast(sessionId, state, data); } - void On(unsigned sessionId, const Hermes::CurrentConfigurationData& data) override { - if (m_parent->m_onCurrentConfiguration) m_parent->m_onCurrentConfiguration(data); + + void On(unsigned sessionId, const SendBoardInfoData& data) override + { + if (m_parent->m_onSendBoardInfo) m_parent->m_onSendBoardInfo(sessionId, data); } - void On(unsigned sessionId, const Hermes::SendHermesCapabilitiesData& data) override { - if (m_parent->m_onSendHermesCapabilities) m_parent->m_onSendHermesCapabilities(data); + + // Auxiliary messages: + void On(unsigned sessionId, const NotificationData& data) override + { + if (m_parent->m_onNotification) m_parent->m_onNotification(sessionId, data); } - void On(unsigned sessionId, const Hermes::NotificationData& data) override { - if (m_parent->m_onNotification) m_parent->m_onNotification(data); + + void On(unsigned sessionId, const CheckAliveData& data) override + { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(sessionId, data); } - void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { - if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); + + void On(unsigned sessionId, const CommandData& data) override + { + if (m_parent->m_onCommand) m_parent->m_onCommand(sessionId, data); } + + Upstream* m_parent; }; - std::atomic m_isRunning; - std::thread m_networkThread; + std::atomic m_isRunning; + std::thread m_networkThread; std::unique_ptr m_callbackWrapper; - std::unique_ptr m_verticalClient; - - ConnectedCallback m_onConnected; - DisconnectedCallback m_onDisconnected; - TraceCallback m_onTrace; - - SupervisoryServiceDescriptionCallback m_onSupervisoryServiceDescription; - BoardArrivedCallback m_onBoardArrived; - BoardDepartedCallback m_onBoardDeparted; - QueryWorkOrderInfoCallback m_onQueryWorkOrderInfo; - ReplyWorkOrderInfoCallback m_onReplyWorkOrderInfo; - CurrentConfigurationCallback m_onCurrentConfiguration; - SendHermesCapabilitiesCallback m_onSendHermesCapabilities; - NotificationCallback m_onNotification; - CheckAliveCallback m_onCheckAlive; + std::unique_ptr m_upstream; + + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + StateChangeCallback m_onStateChange; + TraceCallback m_onTrace; + ServiceDescriptionCallback m_onServiceDescription; + BoardAvailableCallback m_onBoardAvailable; + RevokeBoardAvailableCallback m_onRevokeBoardAvailable; + TransportFinishedCallback m_onTransportFinished; + BoardForecastCallback m_onBoardForecast; + SendBoardInfoCallback m_onSendBoardInfo; + NotificationCallback m_onNotification; + CheckAliveCallback m_onCheckAlive; + CommandCallback m_onCommand; }; } // namespace Modern -} // namespace Hermes \ No newline at end of file +} // namespace Hermes diff --git a/src/include/HermesOptional.hpp b/src/include/HermesOptional.hpp index 17d1122..1f04d8d 100644 --- a/src/include/HermesOptional.hpp +++ b/src/include/HermesOptional.hpp @@ -16,92 +16,20 @@ limitations under the License. // Copyright (c) ASM Assembly Systems GmbH & Co. KG // -// in order to avoid dependencies on boost and >= C++11, we roll out our own stripped down optional class +// FIX: The original file rolled its own Optional class to avoid C++11/Boost +// dependencies. Since this library now requires C++17 (enforced in CMakeLists), +// we use std::optional directly. This removes the custom implementation entirely +// and fixes the two-argument constructor mismatch that caused compile errors in +// HermesDataConversion.hpp (Optional{data.m_pData, data.m_size}). +// #pragma once -#include -#include +#include +#include namespace Hermes { + // Drop-in alias — all code using Hermes::Optional continues to compile. template - class Optional - { - public: - Optional() : m_hasValue(false) {} - - Optional(const T& value) : m_hasValue(true), m_value(value) {} - template - Optional(const U1& u1, const U2& u2) : m_hasValue(true), m_value(u1, u2) {} - - // for the time being, avoid C++11 and its variadic templates for better compatibility. Up to two params should do for Hermes ... - Optional& emplace() { m_value = T(); m_hasValue = true; return *this; } - template - Optional& emplace(const U& u) { m_value = T(u); m_hasValue = true; return *this; } - template - Optional& emplace(const U1& u1, const U2& u2) { m_value = T(u1, u2); m_hasValue = true; return *this; } - - Optional& operator=(const T& value) - { - m_hasValue = true; - m_value = value; - return *this; - } - - const T* operator->() const { assert(m_hasValue); return &m_value; } - T* operator->() { assert(m_hasValue); return &m_value; } - - const T& operator*() const { assert(m_hasValue); return m_value; } - T& operator*() { assert(m_hasValue); return m_value; } - - typedef bool Optional::*UnspecifiedBoolType; - operator UnspecifiedBoolType() const { return m_hasValue ? &Optional::m_hasValue : 0; } - bool operator!() const { return !m_hasValue; } - bool has_value() const { return m_hasValue; } - - const T& value_or(const T& other) const { return m_hasValue ? m_value : other; } - T& value_or(T& other) { return m_hasValue ? m_value : other; } - - void swap(Optional& rhs) - { - using std::swap; - swap(m_hasValue, rhs.m_hasValue); - swap(m_value, rhs.m_value); - } - friend void swap(Optional& lhs, Optional& rhs) { lhs.swap(rhs); } - - void reset() - { - m_hasValue = false; - m_value = T(); - } - - friend bool operator==(const Optional& lhs, const Optional& rhs) - { - if (lhs.m_hasValue && rhs.m_hasValue) - return lhs.m_value == rhs.m_value; - - return lhs.m_hasValue == rhs.m_hasValue; - } - friend bool operator!=(const Optional& lhs, const Optional& rhs) { return !operator==(lhs, rhs); } - - template - friend S& operator<<(S& s, const Optional& o) - { - if (o.m_hasValue) - { - s << o.m_value; - } - else - { - s << ""; - } - return s; - } - - private: - - bool m_hasValue; - T m_value; - }; + using Optional = std::optional; } diff --git a/src/include/HermesStringView.h b/src/include/HermesStringView.h index fc19c8f..d9fa85e 100644 --- a/src/include/HermesStringView.h +++ b/src/include/HermesStringView.h @@ -1,29 +1,26 @@ // Copyright (c) ASM Assembly Systems GmbH & Co. KG // -// C interface helper for Hermes +// C interface string view — a non-owning reference to a character sequence. +// This header is used by both C and C++ translation units. // -#ifndef HERMESSTRINGVIEW_H -#define HERMESSTRINGVIEW_H +#ifndef HERMES_STRING_VIEW_H +#define HERMES_STRING_VIEW_H #include -#include +#include /* FIX: required for size_t */ #ifdef __cplusplus extern "C" { #endif - // Not part of The Hermes Standard, but used extensively: a non-owning string type, in the spirit of std::string_view. - // This relieves us from the need to terminate all strings with \0. - // Note that all the C interface structures are non-owning and need to be backed up by actual storage. - struct HermesStringView - { - const char* m_pData; // if nullptr then we have no string at all - not even an empty string - size_t m_size; - }; +struct HermesStringView +{ + const char* m_pData; + size_t m_size; /* FIX: field was missing in previous version */ +}; #ifdef __cplusplus } #endif -#endif //HERMESSTRINGVIEW_H - +#endif /* HERMES_STRING_VIEW_H */ diff --git a/src/include/HermesStringView.hpp b/src/include/HermesStringView.hpp index 98f05d8..dadafdd 100644 --- a/src/include/HermesStringView.hpp +++ b/src/include/HermesStringView.hpp @@ -15,111 +15,18 @@ limitations under the License. ************************************************************************/ // Copyright (c) ASM Assembly Systems GmbH & Co. KG +// +// FIX: The original file implemented a custom StringView class to avoid +// std::string_view which requires C++17. Since this library now requires +// C++17 (enforced in CMakeLists), we use std::string_view directly. +// All code using Hermes::StringView continues to compile unchanged. +// #pragma once -#include -#include -#include -#include - -// while we have not got std::string_view at our disposal, we make our own: +#include namespace Hermes { - class StringView - { - public: - constexpr StringView() = default; - StringView(const char* pStr) : m_pData(pStr), m_size(std::strlen(pStr)) {} - constexpr StringView(const char* pData, std::size_t size) : m_pData(pData), m_size(size) {} - StringView(const std::string& str) : m_pData(str.data()), m_size(str.size()) {} - StringView& operator=(const std::string& str) - { - m_pData = str.data(); - m_size = str.size(); - return *this; - } - StringView& operator=(const char* pStr) - { - m_pData = pStr; - m_size = ::strlen(pStr); - return *this; - } - - operator std::string() const { return std::string(m_pData, m_size); } - - constexpr const char* data() const { return m_pData; } - constexpr std::size_t size() const { return m_size; } - constexpr std::size_t length() const { return m_size; } - constexpr bool empty() const { return m_size == 0U; } - constexpr StringView substr(std::size_t pos, std::size_t count = std::string::npos) const { return{m_pData + pos, std::min(count, m_size - pos)}; } - - std::size_t find(char c, size_t pos = 0U) - { - if (pos >= m_size) - return std::string::npos; - - auto* pFound = Traits_::find(m_pData + pos, m_size - pos, c); - return pFound ? pFound - m_pData : std::string::npos; - } - - std::size_t find(StringView v) const - { - // empty string always matches: - if (v.empty()) - return 0U; - - // do not bother if size is too large: - if (m_size < v.m_size) - return std::string::npos; - - const char* pMatch; - const char* pLast = m_pData + m_size - v.m_size + 1; - for (auto* p = m_pData; - (pMatch = Traits_::find(p, pLast - p, *v.m_pData)) != nullptr; - p = pMatch + 1) - { - if (Traits_::compare(pMatch, v.m_pData, v.m_size) == 0) - return pMatch - m_pData; - } - return std::string::npos; - } - - - int compare(StringView rhs) - { - auto minSize = m_size < rhs.m_size ? m_size : rhs.m_size; - auto cmp = std::char_traits::compare(m_pData, rhs.m_pData, minSize); - - if (cmp) - return cmp; - if (m_size < rhs.m_size) - return -1; - if (rhs.m_size < m_size) - return 1; - return 0; - } - int compare(std::size_t pos, std::size_t count, StringView rhs) { return substr(pos, count).compare(rhs); } - - friend std::ostream& operator<<(std::ostream& os, StringView sv) - { - os.write(sv.m_pData, sv.m_size); - return os; - } - - private: - const char* m_pData = nullptr; - std::size_t m_size = 0U; - using Traits_ = std::char_traits; - }; - - inline bool operator==(StringView lhs, StringView rhs) - { - return lhs.compare(rhs) == 0; - } - - inline bool operator!=(StringView lhs, StringView rhs) - { - return !operator==(lhs, rhs); - } -} \ No newline at end of file + // Drop-in alias — all code using Hermes::StringView continues to compile. + using StringView = std::string_view; +} From db9acc17becc8a56f7dca655bfed01a7a82c29ee Mon Sep 17 00:00:00 2001 From: Sahil Agarwal Date: Wed, 25 Mar 2026 15:25:25 +0530 Subject: [PATCH 04/14] fix --- src/include/HermesDataConversion.hpp | 406 ++++++++++++++------------- src/include/HermesOptional.hpp | 31 +- 2 files changed, 235 insertions(+), 202 deletions(-) diff --git a/src/include/HermesDataConversion.hpp b/src/include/HermesDataConversion.hpp index 505a9f1..6f20745 100644 --- a/src/include/HermesDataConversion.hpp +++ b/src/include/HermesDataConversion.hpp @@ -23,31 +23,26 @@ limitations under the License. namespace Hermes { // Verify constants match between C and C++ headers - static_assert(cCONFIG_PORT == cHERMES_CONFIG_PORT, ""); - static_assert(cBASE_PORT == cHERMES_BASE_PORT, ""); - static_assert(cMAX_MESSAGE_SIZE == cHERMES_MAX_MESSAGE_SIZE, ""); + static_assert(cCONFIG_PORT == cHERMES_CONFIG_PORT, "config port mismatch"); + static_assert(cBASE_PORT == cHERMES_BASE_PORT, "base port mismatch"); + static_assert(cMAX_MESSAGE_SIZE == cHERMES_MAX_MESSAGE_SIZE, "max message size mismatch"); // ------------------------------------------------------------------------- // String conversions - // FIX: HermesStringView now has m_pData and m_size (fixed in HermesStringView.h) - // FIX: std::optional uses value_or and has_value instead of custom Optional // ------------------------------------------------------------------------- inline void CppToC(const std::string& data, HermesStringView& result) { result.m_pData = data.data(); result.m_size = data.size(); } - inline void CToCpp(HermesStringView data, std::string& result) { result.assign(data.m_pData, data.m_size); } - inline HermesStringView ToC(StringView data) { return { data.data(), data.size() }; } - inline StringView ToCpp(HermesStringView data) { return { data.m_pData, data.m_size }; @@ -55,37 +50,26 @@ namespace Hermes inline void CppToC(const Optional& data, HermesStringView& result) { - if (data.has_value()) - { - result.m_pData = data->data(); - result.m_size = data->size(); - } - else - { - result.m_pData = nullptr; - result.m_size = 0; - } + if (data.has_value()) { result.m_pData = data->data(); result.m_size = data->size(); } + else { result.m_pData = nullptr; result.m_size = 0; } } - inline void CToCpp(HermesStringView data, Optional& result) { - if (data.m_pData) - result = std::string(data.m_pData, data.m_size); - else - result = std::nullopt; + result = data.m_pData ? Optional(std::in_place, data.m_pData, data.m_size) + : std::nullopt; } // ------------------------------------------------------------------------- // Scalar pass-throughs + // FIX: removed separate uint32_t overload — on ARM/RPi, unsigned == uint32_t, + // causing a redefinition error. One overload covers both. // ------------------------------------------------------------------------- - inline void CppToC(double data, double& result) { result = data; } - inline void CToCpp(double data, double& result) { result = data; } - inline void CppToC(unsigned data, unsigned& result) { result = data; } - inline void CToCpp(unsigned data, unsigned& result) { result = data; } + inline void CppToC(double data, double& result) { result = data; } + inline void CToCpp(double data, double& result) { result = data; } inline void CppToC(uint16_t data, uint16_t& result) { result = data; } inline void CToCpp(uint16_t data, uint16_t& result) { result = data; } - inline void CppToC(uint32_t data, uint32_t& result) { result = data; } - inline void CToCpp(uint32_t data, uint32_t& result) { result = data; } + inline void CppToC(unsigned data, unsigned& result) { result = data; } + inline void CToCpp(unsigned data, unsigned& result) { result = data; } // ------------------------------------------------------------------------- // Optional pointer conversions @@ -93,8 +77,7 @@ namespace Hermes template void CToCpp(const CT* pData, CppT& result) { - if (!pData) return; - CToCpp(*pData, result); + if (pData) CToCpp(*pData, result); } template @@ -107,18 +90,9 @@ namespace Hermes void CToCpp(const CT* pData, Optional& result) { if (!pData) { result = std::nullopt; return; } - CppT value{}; - CToCpp(*pData, value); - result = std::move(value); - } - - template - void CppToC(const Optional& data, Optional& result) - { - if (!data.has_value()) { result = std::nullopt; return; } - CT c{}; - CppToC(*data, c); - result = std::move(c); + CppT v{}; + CToCpp(*pData, v); + result = std::move(v); } template @@ -139,15 +113,6 @@ namespace Hermes std::vector m_pointers; }; - template - void CppToC(const std::vector& data, VectorHolder& result) - { - auto sz = data.size(); - result.m_values.resize(sz, CT{}); - for (uint32_t i = 0; i < sz; ++i) - CppToC(data[i], result.m_values[i]); - } - template void CppToC(const std::vector& data, VectorHolder& intermediate, CVector& result) { @@ -159,7 +124,7 @@ namespace Hermes CppToC(data[i], intermediate.m_values[i]); intermediate.m_pointers[i] = &intermediate.m_values[i]; } - result.m_pData = (sz == 0) ? nullptr : intermediate.m_pointers.data(); + result.m_pData = sz == 0 ? nullptr : intermediate.m_pointers.data(); result.m_size = sz; } @@ -172,89 +137,90 @@ namespace Hermes } // ------------------------------------------------------------------------- - // Enum conversions — static_assert verifies C and C++ enums stay in sync + // Enum conversions // ------------------------------------------------------------------------- static_assert(size(EState()) == cHERMES_STATE_ENUM_SIZE, "enum mismatch"); - inline EHermesState ToC(EState data) { return static_cast(data); } - inline EState ToCpp(EHermesState data) { return static_cast(data); } + inline EHermesState ToC(EState d) { return static_cast(d); } + inline EState ToCpp(EHermesState d) { return static_cast(d); } static_assert(size(ETraceType()) == cHERMES_TRACE_TYPE_ENUM_SIZE, "enum mismatch"); - inline EHermesTraceType ToC(ETraceType data) { return static_cast(data); } - inline ETraceType ToCpp(EHermesTraceType data) { return static_cast(data); } + inline EHermesTraceType ToC(ETraceType d) { return static_cast(d); } + inline ETraceType ToCpp(EHermesTraceType d) { return static_cast(d); } static_assert(size(ECheckAliveType()) == cHERMES_CHECK_ALIVE_TYPE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckAliveType data, EHermesCheckAliveType& r) { r = static_cast(data); } - inline void CToCpp(EHermesCheckAliveType data, ECheckAliveType& r) { r = static_cast(data); } + inline void CppToC(ECheckAliveType d, EHermesCheckAliveType& r) { r = static_cast(d); } + inline void CToCpp(EHermesCheckAliveType d, ECheckAliveType& r) { r = static_cast(d); } static_assert(size(EBoardQuality()) == cHERMES_BOARD_QUALITY_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardQuality data, EHermesBoardQuality& r) { r = static_cast(data); } - inline void CToCpp(EHermesBoardQuality data, EBoardQuality& r) { r = static_cast(data); } + inline void CppToC(EBoardQuality d, EHermesBoardQuality& r) { r = static_cast(d); } + inline void CToCpp(EHermesBoardQuality d, EBoardQuality& r) { r = static_cast(d); } static_assert(size(EFlippedBoard()) == cHERMES_FLIPPED_BOARD_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EFlippedBoard data, EHermesFlippedBoard& r) { r = static_cast(data); } - inline void CToCpp(EHermesFlippedBoard data, EFlippedBoard& r) { r = static_cast(data); } + inline void CppToC(EFlippedBoard d, EHermesFlippedBoard& r) { r = static_cast(d); } + inline void CToCpp(EHermesFlippedBoard d, EFlippedBoard& r) { r = static_cast(d); } static_assert(size(ESubBoardState()) == cHERMES_SUB_BOARD_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ESubBoardState data, EHermesSubBoardState& r) { r = static_cast(data); } - inline void CToCpp(EHermesSubBoardState data, ESubBoardState& r) { r = static_cast(data); } + inline void CppToC(ESubBoardState d, EHermesSubBoardState& r) { r = static_cast(d); } + inline void CToCpp(EHermesSubBoardState d, ESubBoardState& r) { r = static_cast(d); } static_assert(size(ETransferState()) == cHERMES_TRANSFER_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ETransferState data, EHermesTransferState& r) { r = static_cast(data); } - inline void CToCpp(EHermesTransferState data, ETransferState& r) { r = static_cast(data); } + inline void CppToC(ETransferState d, EHermesTransferState& r) { r = static_cast(d); } + inline void CToCpp(EHermesTransferState d, ETransferState& r) { r = static_cast(d); } static_assert(size(ENotificationCode()) == cHERMES_NOTIFICATION_CODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ENotificationCode data, EHermesNotificationCode& r) { r = static_cast(data); } - inline void CToCpp(EHermesNotificationCode data, ENotificationCode& r) { r = static_cast(data); } + inline void CppToC(ENotificationCode d, EHermesNotificationCode& r) { r = static_cast(d); } + inline void CToCpp(EHermesNotificationCode d, ENotificationCode& r) { r = static_cast(d); } static_assert(size(ESeverity()) == cHERMES_SEVERITY_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ESeverity data, EHermesSeverity& r) { r = static_cast(data); } - inline void CToCpp(EHermesSeverity data, ESeverity& r) { r = static_cast(data); } + inline void CppToC(ESeverity d, EHermesSeverity& r) { r = static_cast(d); } + inline void CToCpp(EHermesSeverity d, ESeverity& r) { r = static_cast(d); } static_assert(size(ECheckState()) == cHERMES_CHECK_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckState data, EHermesCheckState& r) { r = static_cast(data); } - inline void CToCpp(EHermesCheckState data, ECheckState& r) { r = static_cast(data); } + inline void CppToC(ECheckState d, EHermesCheckState& r) { r = static_cast(d); } + inline void CToCpp(EHermesCheckState d, ECheckState& r) { r = static_cast(d); } static_assert(size(EErrorCode()) == cHERMES_ERROR_CODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EErrorCode data, EHermesErrorCode& r) { r = static_cast(data); } - inline void CToCpp(EHermesErrorCode data, EErrorCode& r) { r = static_cast(data); } + inline void CppToC(EErrorCode d, EHermesErrorCode& r) { r = static_cast(d); } + inline void CToCpp(EHermesErrorCode d, EErrorCode& r) { r = static_cast(d); } static_assert(size(ECheckAliveResponseMode()) == cHERMES_CHECK_ALIVE_RESPONSE_MODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckAliveResponseMode data, EHermesCheckAliveResponseMode& r) { r = static_cast(data); } - inline void CToCpp(EHermesCheckAliveResponseMode data, ECheckAliveResponseMode& r) { r = static_cast(data); } + inline void CppToC(ECheckAliveResponseMode d, EHermesCheckAliveResponseMode& r) { r = static_cast(d); } + inline void CToCpp(EHermesCheckAliveResponseMode d, ECheckAliveResponseMode& r) { r = static_cast(d); } static_assert(size(EBoardArrivedTransfer()) == cHERMES_BOARD_ARRIVED_TRANSFER_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardArrivedTransfer data, EHermesBoardArrivedTransfer& r) { r = static_cast(data); } - inline void CToCpp(EHermesBoardArrivedTransfer data, EBoardArrivedTransfer& r) { r = static_cast(data); } + inline void CppToC(EBoardArrivedTransfer d, EHermesBoardArrivedTransfer& r) { r = static_cast(d); } + inline void CToCpp(EHermesBoardArrivedTransfer d, EBoardArrivedTransfer& r) { r = static_cast(d); } static_assert(size(EBoardDepartedTransfer()) == cHERMES_BOARD_DEPARTED_TRANSFER_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardDepartedTransfer data, EHermesBoardDepartedTransfer& r) { r = static_cast(data); } - inline void CToCpp(EHermesBoardDepartedTransfer data, EBoardDepartedTransfer& r) { r = static_cast(data); } + inline void CppToC(EBoardDepartedTransfer d, EHermesBoardDepartedTransfer& r) { r = static_cast(d); } + inline void CToCpp(EHermesBoardDepartedTransfer d, EBoardDepartedTransfer& r) { r = static_cast(d); } static_assert(size(EReplyWorkOrderInfoStatus()) == cHERMES_REPLY_WORK_ORDER_INFO_STATUS_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EReplyWorkOrderInfoStatus data, EHermesReplyWorkOrderInfoStatus& r) { r = static_cast(data); } - inline void CToCpp(EHermesReplyWorkOrderInfoStatus data, EReplyWorkOrderInfoStatus& r) { r = static_cast(data); } + inline void CppToC(EReplyWorkOrderInfoStatus d, EHermesReplyWorkOrderInfoStatus& r) { r = static_cast(d); } + inline void CToCpp(EHermesReplyWorkOrderInfoStatus d, EReplyWorkOrderInfoStatus& r) { r = static_cast(d); } static_assert(size(EVerticalState()) == cHERMES_VERTICAL_STATE_ENUM_SIZE, "enum mismatch"); - inline EHermesVerticalState ToC(EVerticalState data) { return static_cast(data); } - inline EVerticalState ToCpp(EHermesVerticalState data) { return static_cast(data); } + inline EHermesVerticalState ToC(EVerticalState d) { return static_cast(d); } + inline EVerticalState ToCpp(EHermesVerticalState d) { return static_cast(d); } // ------------------------------------------------------------------------- - // Forward declarations — specialisations defined below + // Forward declarations // ------------------------------------------------------------------------- template struct Converter2C; template struct ConverterBase { ConverterBase() = default; - ConverterBase(const ConverterBase&) = delete; - ConverterBase(ConverterBase&&) = delete; + ConverterBase(const ConverterBase&) = delete; + ConverterBase(ConverterBase&&) = delete; ConverterBase& operator=(const ConverterBase&) = delete; ConverterBase& operator=(ConverterBase&&) = delete; - const T* CPointer() const { return &m_data; } T m_data{}; }; - // UpstreamConfiguration + // ------------------------------------------------------------------------- + // UpstreamConfiguration / DownstreamConfiguration + // ------------------------------------------------------------------------- inline void CppToC(const UpstreamConfiguration& data, HermesUpstreamConfiguration& result) { CppToC(data.m_upstreamLaneId, result.m_upstreamLaneId); @@ -270,7 +236,6 @@ namespace Hermes CToCpp(data.m_port, result.m_port); } - // DownstreamConfiguration inline void CppToC(const DownstreamConfiguration& data, HermesDownstreamConfiguration& result) { CppToC(data.m_downstreamLaneId, result.m_downstreamLaneId); @@ -286,16 +251,18 @@ namespace Hermes CToCpp(data.m_port, result.m_port); } + // ------------------------------------------------------------------------- // SetConfigurationData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const SetConfigurationData& data) { - CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_machineId, m_data.m_machineId); CppToC(data.m_optionalSupervisorySystemPort, m_data.m_pOptionalSupervisorySystemPort); - CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); - CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); + CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); + CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); } private: VectorHolder m_upstreamHolder; @@ -304,19 +271,23 @@ namespace Hermes inline SetConfigurationData ToCpp(const HermesSetConfigurationData& data) { SetConfigurationData result; - CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_machineId, result.m_machineId); CToCpp(data.m_pOptionalSupervisorySystemPort, result.m_optionalSupervisorySystemPort); - CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); - CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); + CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); + CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); return result; } + // ------------------------------------------------------------------------- // GetConfigurationData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const GetConfigurationData&) {} }; inline GetConfigurationData ToCpp(const HermesGetConfigurationData&) { return {}; } + // ------------------------------------------------------------------------- // CurrentConfigurationData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -324,8 +295,8 @@ namespace Hermes { CppToC(data.m_optionalMachineId, m_data.m_optionalMachineId); CppToC(data.m_optionalSupervisorySystemPort, m_data.m_pOptionalSupervisorySystemPort); - CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); - CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); + CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); + CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); } private: VectorHolder m_upstreamHolder; @@ -334,14 +305,16 @@ namespace Hermes inline CurrentConfigurationData ToCpp(const HermesCurrentConfigurationData& data) { CurrentConfigurationData result; - CToCpp(data.m_optionalMachineId, result.m_optionalMachineId); + CToCpp(data.m_optionalMachineId, result.m_optionalMachineId); CToCpp(data.m_pOptionalSupervisorySystemPort, result.m_optionalSupervisorySystemPort); - CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); - CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); + CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); + CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); return result; } + // ------------------------------------------------------------------------- // ConnectionInfo + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -361,7 +334,10 @@ namespace Hermes return result; } - // FIX: Was Converter2C — typo, must be Converter2C + // ------------------------------------------------------------------------- + // Error + // FIX: was Converter2C (typo) — corrected to Converter2C + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -379,17 +355,19 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // CheckAliveData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const CheckAliveData& data) { - CppToC(data.m_optionalType, m_optionalType, m_data.m_pOptionalType); + CppToC(data.m_optionalType, m_optType, m_data.m_pOptionalType); CppToC(data.m_optionalId, m_data.m_optionalId); } private: - EHermesCheckAliveType m_optionalType{}; + EHermesCheckAliveType m_optType{}; }; inline CheckAliveData ToCpp(const HermesCheckAliveData& data) { @@ -399,7 +377,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // NotificationData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -419,17 +399,19 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // ServiceDescriptionData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const ServiceDescriptionData& data) { - CppToC(data.m_machineId, m_data.m_machineId); - CppToC(data.m_laneId, m_data.m_laneId); + CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_laneId, m_data.m_laneId); CppToC(data.m_optionalInterfaceId, m_data.m_optionalInterfaceId); CppToC(data.m_version, m_data.m_version); - m_data.m_pSupportedFeatures = nullptr; // features are optional, not mapped here + m_data.m_pSupportedFeatures = nullptr; } }; inline ServiceDescriptionData ToCpp(const HermesServiceDescriptionData& data) @@ -442,13 +424,10 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // SubBoard / SubBoards helper - struct SubBoardsHolder - { - VectorHolder m_holder; - HermesSubBoards m_data{}; - }; - + // SubBoards is typedef'd as std::vector in HermesData.hpp + // ------------------------------------------------------------------------- inline void CppToC(const SubBoard& data, HermesSubBoard& result) { CppToC(data.m_pos, result.m_pos); @@ -462,141 +441,163 @@ namespace Hermes CToCpp(data.m_st, result.m_st); } - inline void CppToC(const std::vector& data, SubBoardsHolder& holder) + struct SubBoardsHolder + { + VectorHolder m_holder; + HermesSubBoards m_data{}; + }; + inline void CppToC(const SubBoards& data, SubBoardsHolder& holder) { CppToC(data, holder.m_holder, holder.m_data); } - inline void CToCpp(const HermesSubBoards& data, std::vector& result) + inline void CToCpp(const HermesSubBoards& data, SubBoards& result) { CToCpp(data, result); } + // ------------------------------------------------------------------------- // BoardAvailableData + // FIX: field names corrected to match HermesData.hpp: + // m_length -> m_optionalLengthInMM + // m_width -> m_optionalWidthInMM + // etc. + // m_subBoards -> m_optionalSubBoards + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const BoardAvailableData& data) { - CppToC(data.m_boardId, m_data.m_boardId); - CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_flippedBoard, m_data.m_flippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_length, m_optLength, m_data.m_pOptionalLengthInMM); - CppToC(data.m_width, m_optWidth, m_data.m_pOptionalWidthInMM); - CppToC(data.m_thickness, m_optThick, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_topClearanceHeight, m_optTopClear, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_bottomClearanceHeight, m_optBotClear, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_weight, m_optWeight, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); - CppToC(data.m_optionalRoute, m_optRoute, m_data.m_pOptionalRoute); - CppToC(data.m_optionalAction, m_optAction, m_data.m_pOptionalAction); - CppToC(data.m_subBoards, m_subBoardsHolder); - m_data.m_optionalSubBoards = m_subBoardsHolder.m_data; + CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_flippedBoard, m_data.m_flippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_optionalLengthInMM, m_optLength, m_data.m_pOptionalLengthInMM); + CppToC(data.m_optionalWidthInMM, m_optWidth, m_data.m_pOptionalWidthInMM); + CppToC(data.m_optionalThicknessInMM, m_optThick, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_optionalTopClearanceHeightInMM, m_optTopCl, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_optionalBottomClearanceHeightInMM, m_optBotCl, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_optionalWeightInGrams, m_optWeight, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_optionalRoute, m_optRoute, m_data.m_pOptionalRoute); + CppToC(data.m_optionalAction, m_optAction, m_data.m_pOptionalAction); + CppToC(data.m_optionalSubBoards, m_subBoards); + m_data.m_optionalSubBoards = m_subBoards.m_data; } private: double m_optLength{}, m_optWidth{}, m_optThick{}, m_optSpeed{}; - double m_optTopClear{}, m_optBotClear{}, m_optWeight{}; + double m_optTopCl{}, m_optBotCl{}, m_optWeight{}; uint16_t m_optRoute{}, m_optAction{}; - SubBoardsHolder m_subBoardsHolder; + SubBoardsHolder m_subBoards; }; inline BoardAvailableData ToCpp(const HermesBoardAvailableData& data) { BoardAvailableData result; - CToCpp(data.m_boardId, result.m_boardId); - CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); - CToCpp(data.m_failedBoard, result.m_failedBoard); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_flippedBoard, result.m_flippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_length); - CToCpp(data.m_pOptionalWidthInMM, result.m_width); - CToCpp(data.m_pOptionalThicknessInMM, result.m_thickness); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_topClearanceHeight); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_bottomClearanceHeight); - CToCpp(data.m_pOptionalWeightInGrams, result.m_weight); + CToCpp(data.m_boardId, result.m_boardId); + CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); + CToCpp(data.m_failedBoard, result.m_failedBoard); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_flippedBoard, result.m_flippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); + CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); + CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM,result.m_optionalBottomClearanceHeightInMM); + CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); CToCpp(data.m_pOptionalAction, result.m_optionalAction); - CToCpp(data.m_optionalSubBoards, result.m_subBoards); + CToCpp(data.m_optionalSubBoards, result.m_optionalSubBoards); return result; } + // ------------------------------------------------------------------------- // RevokeBoardAvailableData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const RevokeBoardAvailableData&) {} }; inline RevokeBoardAvailableData ToCpp(const HermesRevokeBoardAvailableData&) { return {}; } + // ------------------------------------------------------------------------- // MachineReadyData + // FIX: field names corrected (m_length -> m_optionalLengthInMM, etc.) + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const MachineReadyData& data) { - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalForecastId, m_data.m_optionalForecastId); - CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_optionalFlippedBoard, m_optFlipped, m_data.m_pOptionalFlippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_length, m_optLength, m_data.m_pOptionalLengthInMM); - CppToC(data.m_width, m_optWidth, m_data.m_pOptionalWidthInMM); - CppToC(data.m_thickness, m_optThick, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_topClearanceHeight, m_optTopClear, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_bottomClearanceHeight, m_optBotClear, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_weight, m_optWeight, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalForecastId, m_data.m_optionalForecastId); + CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_optionalFlippedBoard, m_optFlipped, m_data.m_pOptionalFlippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_optionalLengthInMM, m_optLength, m_data.m_pOptionalLengthInMM); + CppToC(data.m_optionalWidthInMM, m_optWidth, m_data.m_pOptionalWidthInMM); + CppToC(data.m_optionalThicknessInMM, m_optThick, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_optionalTopClearanceHeightInMM, m_optTopCl, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_optionalBottomClearanceHeightInMM,m_optBotCl, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_optionalWeightInGrams, m_optWeight, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); } private: EHermesFlippedBoard m_optFlipped{}; double m_optLength{}, m_optWidth{}, m_optThick{}, m_optSpeed{}; - double m_optTopClear{}, m_optBotClear{}, m_optWeight{}; + double m_optTopCl{}, m_optBotCl{}, m_optWeight{}; }; inline MachineReadyData ToCpp(const HermesMachineReadyData& data) { MachineReadyData result; - CToCpp(data.m_failedBoard, result.m_failedBoard); - CToCpp(data.m_optionalForecastId, result.m_optionalForecastId); - CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_length); - CToCpp(data.m_pOptionalWidthInMM, result.m_width); - CToCpp(data.m_pOptionalThicknessInMM, result.m_thickness); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_topClearanceHeight); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM,result.m_bottomClearanceHeight); - CToCpp(data.m_pOptionalWeightInGrams, result.m_weight); - CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + CToCpp(data.m_failedBoard, result.m_failedBoard); + CToCpp(data.m_optionalForecastId, result.m_optionalForecastId); + CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); + CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); + CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM,result.m_optionalBottomClearanceHeightInMM); + CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); + CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); return result; } + // ------------------------------------------------------------------------- // RevokeMachineReadyData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const RevokeMachineReadyData&) {} }; inline RevokeMachineReadyData ToCpp(const HermesRevokeMachineReadyData&) { return {}; } + // ------------------------------------------------------------------------- // StartTransportData + // FIX: field name corrected: m_conveyorSpeed -> m_optionalConveyorSpeedInMMPerSecs + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const StartTransportData& data) { - CppToC(data.m_boardId, m_data.m_boardId); - CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); } private: double m_optSpeed{}; @@ -605,11 +606,13 @@ namespace Hermes { StartTransportData result; CToCpp(data.m_boardId, result.m_boardId); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); return result; } + // ------------------------------------------------------------------------- // StopTransportData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -627,7 +630,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // TransportFinishedData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -645,7 +650,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // QueryBoardInfoData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -663,14 +670,13 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // CommandData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { - explicit Converter2C(const CommandData& data) - { - CppToC(data.m_command, m_data.m_command); - } + explicit Converter2C(const CommandData& data) { CppToC(data.m_command, m_data.m_command); } }; inline CommandData ToCpp(const HermesCommandData& data) { @@ -679,7 +685,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // UpstreamSettings + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -707,7 +715,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // DownstreamSettings + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -735,7 +745,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // ConfigurationServiceSettings + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -753,7 +765,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // VerticalServiceSettings + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -777,7 +791,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // VerticalClientSettings + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -803,4 +819,4 @@ namespace Hermes return result; } -} // namespace Hermes +} // namespace Hermes \ No newline at end of file diff --git a/src/include/HermesOptional.hpp b/src/include/HermesOptional.hpp index 1f04d8d..b7a5c1a 100644 --- a/src/include/HermesOptional.hpp +++ b/src/include/HermesOptional.hpp @@ -16,11 +16,15 @@ limitations under the License. // Copyright (c) ASM Assembly Systems GmbH & Co. KG // -// FIX: The original file rolled its own Optional class to avoid C++11/Boost -// dependencies. Since this library now requires C++17 (enforced in CMakeLists), -// we use std::optional directly. This removes the custom implementation entirely -// and fixes the two-argument constructor mismatch that caused compile errors in -// HermesDataConversion.hpp (Optional{data.m_pData, data.m_size}). +// FIX: Replaced the custom Optional class with std::optional. +// The library requires C++17 (enforced in CMakeLists.txt), so std::optional +// is always available. +// +// FIX: Added operator<< for std::optional in the Hermes namespace. +// The internal implementation files (AsioServer.cpp etc.) use BuildString() +// which calls operator<< on every argument. The old custom Optional had +// operator<< defined on it. std::optional does not. Adding it here restores +// that behaviour without modifying any internal .cpp files. // #pragma once @@ -29,7 +33,20 @@ limitations under the License. namespace Hermes { - // Drop-in alias — all code using Hermes::Optional continues to compile. + // Drop-in alias — all code using Hermes::Optional compiles unchanged. template using Optional = std::optional; -} + + // operator<< for Hermes::Optional (= std::optional). + // Required by internal StringBuilder usage in AsioServer.cpp and friends. + // Prints the contained value if present, or "" if not. + template + S& operator<<(S& s, const std::optional& o) + { + if (o.has_value()) + s << *o; + else + s << ""; + return s; + } +} \ No newline at end of file From 5207d05f957aa2a9d5cc8f1fb0ae8b0140698c3e Mon Sep 17 00:00:00 2001 From: Sahil Agarwal Date: Wed, 25 Mar 2026 15:33:30 +0530 Subject: [PATCH 05/14] fix2 --- README.md | 1401 +++++++++++++++----------- src/include/HermesDataConversion.hpp | 2 +- src/include/HermesOptional.hpp | 2 +- 3 files changed, 798 insertions(+), 607 deletions(-) diff --git a/README.md b/README.md index d54faca..ec6d0a1 100644 --- a/README.md +++ b/README.md @@ -1,449 +1,432 @@ -# Hermes C++ Library — Complete Reference +# Hermes Standard Library — Complete API Reference -**Protocol:** The Hermes Standard — vendor-independent machine-to-machine communication for SMT assembly lines -**License:** Apache 2.0 +**Version:** 1.0 +**License:** Apache 2.0 — Copyright 2018 ASM Assembly Systems GmbH & Co. KG +**Protocol:** The Hermes Standard — vendor-independent machine-to-machine communication for SMT assembly **Requires:** C++17, Boost 1.66+, CMake 3.15+ --- ## Table of Contents -1. [What is Hermes](#1-what-is-hermes) -2. [Architecture overview](#2-architecture-overview) -3. [Building the library](#3-building-the-library) -4. [Header map — what to include](#4-header-map) -5. [Core types reference](#5-core-types-reference) -6. [Modern C++ API — HermesModern.hpp](#6-modern-c-api) -7. [Low-level C++ API — Hermes.hpp](#7-low-level-c-api) -8. [Serialization API — HermesSerialization.hpp](#8-serialization-api) -9. [Configuration service](#9-configuration-service) -10. [Vertical interface](#10-vertical-interface) -11. [Complete examples](#11-complete-examples) -12. [Bugs fixed in this version](#12-bugs-fixed) +1. [Protocol primer](#1-protocol-primer) +2. [Building](#2-building) +3. [C API — Hermes.h](#3-c-api) +4. [C++ API — Hermes.hpp](#4-c-api-1) +5. [Modern C++ API — HermesModern.hpp](#5-modern-c-api) +6. [Serialization API — HermesSerialization.h/.hpp](#6-serialization-api) +7. [Data types reference — HermesData.h / HermesData.hpp](#7-data-types-reference) +8. [Error handling](#8-error-handling) +9. [Thread safety rules](#9-thread-safety-rules) +10. [Complete examples](#10-complete-examples) +11. [Bugs fixed in this release](#11-bugs-fixed) --- -## 1. What is Hermes +## 1. Protocol primer -Hermes is an open TCP/IP + XML protocol that connects machines in an electronics assembly line. Each machine has an **Upstream** port (faces the previous machine) and a **Downstream** port (faces the next machine). PCB boards flow from Upstream to Downstream along the lane. +Hermes connects machines in a PCB assembly line over TCP/IP. Each machine has a **Downstream port** (server — listens for the previous machine) and an **Upstream port** (client — connects to the next machine). ``` -[Machine A] --downstream:50100--> [Machine B] --downstream:50101--> [Machine C] - <--upstream:50100--- <--upstream:50101--- +[Machine A]──Upstream──connects to──Downstream──[Machine B]──Upstream──connects to──Downstream──[Machine C] + port 50100 port 50101 ``` -The Downstream machine **listens** on a port. The Upstream machine **connects** to it. So: +**Session:** every TCP connect/disconnect cycle increments the session ID (starts at 1). All callbacks receive a `sessionId` so you can correlate events. -- `Hermes::Downstream` / `Modern::Downstream` — **server role**, listens for incoming connections -- `Hermes::Upstream` / `Modern::Upstream` — **client role**, connects to the downstream machine - ---- - -## 2. Architecture overview - -``` -Your application - | - |--- HermesModern.hpp (std::function callbacks — recommended) - |--- Hermes.hpp (virtual interface callbacks — advanced) - |--- HermesSerialization (XML serialize/deserialize) - | - +---> Hermes C API (Hermes.h / HermesData.h) - | compiled into libhermes.so - | - +---> Boost.Asio (networking) - +---> pugixml (XML parsing) -``` - -### Message flow — horizontal (machine to machine) +**Message flow for a board transfer:** ``` -Downstream machine Upstream machine -(Modern::Downstream) (Modern::Upstream) - | | - |<-- TCP connect -------------------- | - | | - |<-- ServiceDescription ------------- | (Upstream identifies itself) - |--> ServiceDescription -----------> | (Downstream identifies itself) - | | - |<-- MachineReady ------------------- | (Upstream ready to receive) - |--> BoardAvailable --------------> | (Downstream has a board) - | | - |<-- StartTransport ----------------- | (Upstream says: send it) - | [board physically moves] | - |--> TransportFinished -----------> | (Downstream confirms done) - | | - |<-- StopTransport ---------------- | (Upstream confirms received) +Downstream (server) Upstream (client) + │◄──── TCP connect ──────────│ + │◄──── ServiceDescription ───│ Upstream identifies itself + │───── ServiceDescription ──►│ Downstream identifies itself + │◄──── MachineReady ─────────│ Upstream ready to receive + │───── BoardAvailable ───────►│ Downstream has a board + │◄──── StartTransport ────────│ Upstream says: send it + │ [board physically moves] │ + │───── TransportFinished ─────►│ Downstream: done + │◄──── StopTransport ─────────│ Upstream: confirmed received ``` -### Who receives what +**Who receives what:** -| You create | You listen for (receive) | You send | -|-------------------|-------------------------------------------------------------------|-------------------------------------------------------| -| `Modern::Downstream` | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | -| `Modern::Upstream` | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | +| Role | Receives FROM peer | Sends TO peer | +|------|--------------------|---------------| +| Downstream (server) | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | +| Upstream (client) | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | --- -## 3. Building the library - -### Prerequisites +## 2. Building ```bash -# Debian / Ubuntu / Raspberry Pi OS +# Install dependencies (Raspberry Pi OS / Debian / Ubuntu) sudo apt install -y g++ cmake libboost-all-dev -# macOS -brew install cmake boost - -# Windows -# Install Boost via vcpkg: vcpkg install boost -``` - -### Build - -```bash +# Build the library git clone https://github.com/hermes-org/lib_cpp.git cd lib_cpp mkdir build && cd build cmake .. make -j4 -``` - -This produces `build/src/Hermes/libhermes.so` (Linux/macOS) or `hermes.dll` (Windows). -### Compile your application +# Find the shared library +find . -name "libhermes.so" +# → ./src/Hermes/libhermes.so -```bash +# Compile your application g++ -std=c++17 -o myapp myapp.cpp \ -I./src/include \ -L./build/src/Hermes -lhermes \ -lboost_system -lpthread -# Run (Linux — tell the linker where to find the .so) +# Run LD_LIBRARY_PATH=./build/src/Hermes ./myapp ``` --- -## 4. Header map - -| Header | What it gives you | When to include it | -|--------|------------------|--------------------| -| `HermesModern.hpp` | `Modern::Downstream`, `Modern::Upstream` with std::function callbacks | **Start here. Recommended for all new code.** | -| `Hermes.hpp` | `Hermes::Downstream`, `Hermes::Upstream`, virtual callback interfaces | When you need session IDs, raw XML, or fine-grained control | -| `HermesData.hpp` | All C++ data structs, enums, settings structs | Included automatically by the above | -| `HermesSerialization.hpp` | `ToXml()`, `FromXml()` | When you need to inspect or log raw XML messages | -| `HermesOptional.hpp` | `Hermes::Optional` = `std::optional` | Included automatically | -| `HermesStringView.hpp` | `Hermes::StringView` = `std::string_view` | Included automatically | - ---- +## 3. C API -## 5. Core types reference +**Header:** `Hermes.h` (includes `HermesData.h` and `HermesStringView.h`) +**Link:** `-lhermes` -### EState — connection state machine +The C API uses opaque handles and function-pointer callbacks. Every callback struct has the same shape: -```cpp -enum class EState { - eNOT_CONNECTED, // No TCP connection - eSOCKET_CONNECTED, // TCP connected, waiting for ServiceDescription - eSERVICE_DESCRIPTION_DOWNSTREAM, // ServiceDescription exchanged - eNOT_AVAILABLE_NOT_READY, // Connected, neither side ready - eBOARD_AVAILABLE, // Downstream has a board waiting - eMACHINE_READY, // Upstream is ready to receive - eAVAILABLE_AND_READY, // Both sides ready — transport can start - eTRANSPORTING, // Board is moving - eTRANSPORT_STOPPED, // Transport paused - eTRANSPORT_FINISHED, // Board delivered - eDISCONNECTED // Connection closed +```c +struct HermesXxxCallback { + void (*m_pCall)(void* m_pData, /* message params */); + void* m_pData; // passed back as first arg to m_pCall }; ``` -### ETraceType — log levels +### 3.1 Constants -```cpp -enum class ETraceType { - eSENT, // Raw XML sent over the wire - eRECEIVED, // Raw XML received - eDEBUG, - eINFO, - eWARNING, - eERROR -}; +```c +static const uint16_t cHERMES_BASE_PORT = 50100U; // default machine port +static const uint16_t cHERMES_CONFIG_PORT = 1248U; // configuration service port +static const unsigned cHERMES_MAX_MESSAGE_SIZE = 65536U; ``` -### Error +### 3.2 HermesStringView -```cpp -struct Error { - EErrorCode m_code; // eSUCCESS, eNETWORK_ERROR, eTIMEOUT, etc. - std::string m_text; // Human-readable description +Non-owning string (like `std::string_view`). All C structs use this. **Does not own the memory.** - explicit operator bool() const; // true if error (code != eSUCCESS) +```c +struct HermesStringView { + const char* m_pData; // NULL means "no string" (not even empty) + size_t m_size; }; ``` -### ConnectionInfo +### 3.3 Downstream API -```cpp -struct ConnectionInfo { - std::string m_address; // Remote IP address - uint16_t m_port; // Remote port - std::string m_hostName; // Remote hostname (if resolvable) -}; -``` +**Lifecycle:** -### ServiceDescriptionData +```c +// 1. Create +HermesDownstream* pDs = CreateHermesDownstream( + uint32_t laneId, // lane number (1-based) + const HermesDownstreamCallbacks* pCb // all callbacks +); -Exchanged in both directions at connection start. Identifies the machine. +// 2. Enable (start listening) +HermesDownstreamSettings settings = {0}; +settings.m_machineId = {"MyMachine", 9}; +settings.m_port = 50100; +settings.m_checkAlivePeriodInSeconds = 60.0; +settings.m_reconnectWaitTimeInSeconds = 10.0; +EnableHermesDownstream(pDs, &settings); -```cpp -struct ServiceDescriptionData { - std::string m_machineId; // Required. Your machine identifier. - uint32_t m_laneId; // Required. Which lane (1-based). - Optional m_optionalInterfaceId; // Optional. Interface label. - std::string m_version; // Protocol version string. -}; -``` +// 3. Run (blocks until Stop is called from another thread) +RunHermesDownstream(pDs); -### BoardAvailableData +// 4. Stop (call from another thread or from a PostHermesDownstream callback) +StopHermesDownstream(pDs); -Sent by Downstream to signal a board is ready for transfer. +// 5. Delete (after Run returns) +DeleteHermesDownstream(pDs); +``` -```cpp -struct BoardAvailableData { - std::string m_boardId; // Required. Unique board identifier. - std::string m_boardIdCreatedBy; // Required. Machine that created the ID. - EBoardQuality m_failedBoard; // eANY, eGOOD, eBAD - Optional m_optionalProductTypeId; - EFlippedBoard m_flippedBoard; // eSIDE_UP_IS_UNKNOWN, eTOP_SIDE_IS_UP, eBOTTOM_SIDE_IS_UP - Optional m_optionalTopBarcode; - Optional m_optionalBottomBarcode; - Optional m_length; // mm - Optional m_width; // mm - Optional m_thickness; // mm - Optional m_conveyorSpeed; // mm/s - Optional m_topClearanceHeight; // mm - Optional m_bottomClearanceHeight; // mm - Optional m_weight; // grams - Optional m_optionalWorkOrderId; - Optional m_optionalBatchId; - Optional m_optionalRoute; - Optional m_optionalAction; - std::vector m_subBoards; +**Callbacks to register in `HermesDownstreamCallbacks`:** + +```c +struct HermesDownstreamCallbacks { + HermesConnectedCallback m_connectedCallback; // TCP connected + HermesServiceDescriptionCallback m_serviceDescriptionCallback; // upstream identified itself + HermesMachineReadyCallback m_machineReadyCallback; // upstream ready to receive + HermesRevokeMachineReadyCallback m_revokeMachineReadyCallback; // upstream cancels ready + HermesStartTransportCallback m_startTransportCallback; // upstream says: send board + HermesStopTransportCallback m_stopTransportCallback; // upstream confirms received + HermesQueryBoardInfoCallback m_queryBoardInfoCallback; // upstream asks board info + HermesNotificationCallback m_notificationCallback; // protocol notification + HermesStateCallback m_stateCallback; // state machine changed + HermesCheckAliveCallback m_checkAliveCallback; // keepalive ping/pong + HermesCommandCallback m_commandCallback; // vendor command + HermesDisconnectedCallback m_disconnectedCallback; // TCP disconnected + HermesTraceCallback m_traceCallback; // raw XML / debug log }; ``` -### MachineReadyData +**Signals (send to upstream):** -Sent by Upstream to signal it is ready to receive a board. +```c +SignalHermesDownstreamServiceDescription(pDs, sessionId, const HermesServiceDescriptionData*); +SignalHermesBoardAvailable(pDs, sessionId, const HermesBoardAvailableData*); +SignalHermesRevokeBoardAvailable(pDs, sessionId, const HermesRevokeBoardAvailableData*); +SignalHermesTransportFinished(pDs, sessionId, const HermesTransportFinishedData*); +SignalHermesBoardForecast(pDs, sessionId, const HermesBoardForecastData*); +SignalHermesSendBoardInfo(pDs, sessionId, const HermesSendBoardInfoData*); +SignalHermesDownstreamNotification(pDs, sessionId, const HermesNotificationData*); +SignalHermesDownstreamCheckAlive(pDs, sessionId, const HermesCheckAliveData*); +SignalHermesDownstreamCommand(pDs, sessionId, const HermesCommandData*); -```cpp -struct MachineReadyData { - EBoardQuality m_failedBoard; // What quality board it can accept - Optional m_optionalForecastId; - Optional m_optionalBoardId; - Optional m_optionalProductTypeId; - Optional m_optionalFlippedBoard; - Optional m_optionalTopBarcode; - Optional m_optionalBottomBarcode; - Optional m_length; - Optional m_width; - Optional m_thickness; - Optional m_conveyorSpeed; - Optional m_topClearanceHeight; - Optional m_bottomClearanceHeight; - Optional m_weight; - Optional m_optionalWorkOrderId; - Optional m_optionalBatchId; -}; -``` +// Reset to initial state (sends notification then resets state machine) +ResetHermesDownstream(pDs, const HermesNotificationData*); -### StartTransportData / StopTransportData / TransportFinishedData +// Disable (stop accepting new connections, notify current peer) +DisableHermesDownstream(pDs, const HermesNotificationData*); -```cpp -struct StartTransportData { - std::string m_boardId; // Which board to transport - Optional m_conveyorSpeed; // mm/s, optional override -}; +// Testing only — send raw XML string +SignalHermesDownstreamRawXml(pDs, sessionId, HermesStringView rawXml); +ResetHermesDownstreamRawXml(pDs, HermesStringView rawXml); +``` -struct StopTransportData { - ETransferState m_transferState; // eCOMPLETE, eINCOMPLETE, eNOT_STARTED - std::string m_boardId; -}; +**Post a task onto the Hermes event thread (thread-safe):** -struct TransportFinishedData { - ETransferState m_transferState; - std::string m_boardId; +```c +struct HermesVoidCallback { + void (*m_pCall)(void* m_pData); + void* m_pData; }; +PostHermesDownstream(pDs, HermesVoidCallback); ``` -### NotificationData +### 3.4 Upstream API -Sent by either side to report errors or status. +**Lifecycle:** -```cpp -struct NotificationData { - ENotificationCode m_notificationCode; // ePROTOCOL_ERROR, eMACHINE_SHUTDOWN, etc. - ESeverity m_severity; // eFATAL, eERROR, eWARNING, eINFO - std::string m_description; // Human-readable message -}; -``` +```c +HermesUpstream* pUs = CreateHermesUpstream(laneId, const HermesUpstreamCallbacks*); -### Settings structs +HermesUpstreamSettings settings = {0}; +settings.m_machineId = {"MyMachine", 9}; +settings.m_hostAddress = {"192.168.1.2", 11}; // downstream machine IP +settings.m_port = 50100; +settings.m_checkAlivePeriodInSeconds = 60.0; +settings.m_reconnectWaitTimeInSeconds = 10.0; +EnableHermesUpstream(pUs, &settings); -```cpp -struct DownstreamSettings { - std::string m_machineId; // Required - Optional m_optionalClientAddress; // Restrict to one client IP - uint16_t m_port{0}; // 0 = use cBASE_PORT (50100) - double m_checkAlivePeriodInSeconds{60}; - double m_reconnectWaitTimeInSeconds{10}; - ECheckAliveResponseMode m_checkAliveResponseMode{eAUTO}; - ECheckState m_checkState{eSEND_AND_RECEIVE}; -}; +RunHermesUpstream(pUs); // blocks +StopHermesUpstream(pUs); +DeleteHermesUpstream(pUs); +``` -struct UpstreamSettings { - std::string m_machineId; // Required - std::string m_hostAddress; // Required. IP of the downstream machine. - uint16_t m_port{0}; // 0 = use cBASE_PORT (50100) - double m_checkAlivePeriodInSeconds{60}; - double m_reconnectWaitTimeInSeconds{10}; - ECheckAliveResponseMode m_checkAliveResponseMode{eAUTO}; - ECheckState m_checkState{eSEND_AND_RECEIVE}; +**Callbacks in `HermesUpstreamCallbacks`:** + +```c +struct HermesUpstreamCallbacks { + HermesConnectedCallback m_connectedCallback; + HermesServiceDescriptionCallback m_serviceDescriptionCallback; // downstream identified + HermesBoardAvailableCallback m_boardAvailableCallback; // downstream has board + HermesRevokeBoardAvailableCallback m_revokeBoardAvailableCallback; + HermesTransportFinishedCallback m_transportFinishedCallback; // downstream done + HermesBoardForecastCallback m_boardForecastCallback; // board coming soon + HermesSendBoardInfoCallback m_sendBoardInfoCallback; // board metadata + HermesNotificationCallback m_notificationCallback; + HermesStateCallback m_stateCallback; + HermesCheckAliveCallback m_checkAliveCallback; + HermesCommandCallback m_commandCallback; + HermesDisconnectedCallback m_disconnectedCallback; + HermesTraceCallback m_traceCallback; }; ``` ---- +**Signals (send to downstream):** + +```c +SignalHermesUpstreamServiceDescription(pUs, sessionId, const HermesServiceDescriptionData*); +SignalHermesMachineReady(pUs, sessionId, const HermesMachineReadyData*); +SignalHermesRevokeMachineReady(pUs, sessionId, const HermesRevokeMachineReadyData*); +SignalHermesStartTransport(pUs, sessionId, const HermesStartTransportData*); +SignalHermesStopTransport(pUs, sessionId, const HermesStopTransportData*); +SignalHermesQueryBoardInfo(pUs, sessionId, const HermesQueryBoardInfoData*); +SignalHermesUpstreamNotification(pUs, sessionId, const HermesNotificationData*); +SignalHermesUpstreamCheckAlive(pUs, sessionId, const HermesCheckAliveData*); +SignalHermesUpstreamCommand(pUs, sessionId, const HermesCommandData*); +ResetHermesUpstream(pUs, const HermesNotificationData*); +DisableHermesUpstream(pUs, const HermesNotificationData*); +PostHermesUpstream(pUs, HermesVoidCallback); +``` -## 6. Modern C++ API +### 3.5 Configuration client API -`HermesModern.hpp` provides `Modern::Downstream` and `Modern::Upstream`. These wrap the low-level virtual callback interface with `std::function` callbacks. This is the recommended API for new code. +Get or set a machine's Hermes lane configuration over TCP (port 1248). -### Modern::Downstream +```c +// Get configuration (synchronous, blocks until complete or timeout) +struct HermesGetConfigurationCallbacks { + HermesCurrentConfigurationCallback m_currentConfigurationCallback; + HermesErrorCallback m_errorCallback; + HermesTraceCallback m_traceCallback; +}; +GetHermesConfiguration( + HermesStringView hostName, // e.g. "192.168.1.2" + unsigned timeoutInSeconds, // e.g. 10 + const HermesGetConfigurationCallbacks* +); -```cpp -Hermes::Modern::Downstream ds(unsigned laneId); +// Set configuration (synchronous) +struct HermesSetConfigurationCallbacks { + HermesCurrentConfigurationCallback m_currentConfigurationCallback; + HermesErrorCallback m_errorCallback; + HermesNotificationCallback m_notificationCallback; + HermesTraceCallback m_traceCallback; +}; +SetHermesConfiguration( + HermesStringView hostName, + const HermesSetConfigurationData*, + unsigned timeoutInSeconds, + const HermesSetConfigurationCallbacks* +); ``` -**Register callbacks:** +### 3.6 Configuration service API -```cpp -// Connection events — sessionId identifies the active connection -ds.RegisterConnectedCallback( [](unsigned sessionId, const ConnectionInfo& info) { ... }); -ds.RegisterDisconnectedCallback([](unsigned sessionId, const Error& err) { ... }); -ds.RegisterStateChangeCallback( [](unsigned sessionId, EState state) { ... }); -ds.RegisterTraceCallback( [](unsigned sessionId, ETraceType type, StringView msg) { ... }); - -// Messages received FROM the Upstream machine -ds.RegisterServiceDescriptionCallback( [](unsigned sessionId, EState, const ServiceDescriptionData& d) { ... }); -ds.RegisterMachineReadyCallback( [](unsigned sessionId, EState, const MachineReadyData& d) { ... }); -ds.RegisterRevokeMachineReadyCallback( [](unsigned sessionId, EState, const RevokeMachineReadyData& d) { ... }); -ds.RegisterStartTransportCallback( [](unsigned sessionId, EState, const StartTransportData& d) { ... }); -ds.RegisterStopTransportCallback( [](unsigned sessionId, EState, const StopTransportData& d) { ... }); -ds.RegisterQueryBoardInfoCallback( [](unsigned sessionId, const QueryBoardInfoData& d) { ... }); - -// Auxiliary -ds.RegisterNotificationCallback([](unsigned sessionId, const NotificationData& d) { ... }); -ds.RegisterCheckAliveCallback( [](unsigned sessionId, const CheckAliveData& d) { ... }); -ds.RegisterCommandCallback( [](unsigned sessionId, const CommandData& d) { ... }); -``` - -**Lifecycle:** +Expose a configuration service on the machine (port 1248) so remote tools can query/set config. -```cpp -DownstreamSettings settings; -settings.m_machineId = "MyMachine"; -settings.m_port = 50100; +```c +HermesConfigurationService* pSvc = CreateHermesConfigurationService( + const HermesConfigurationServiceCallbacks* +); -ds.Enable(settings); // starts listening, launches network thread -// ... application runs ... -ds.Stop(); // stops network thread, closes connection -``` +struct HermesConfigurationServiceCallbacks { + HermesConnectedCallback m_connectedCallback; + HermesSetConfigurationCallback m_setConfigurationCallback; + HermesGetConfigurationCallback m_getConfigurationCallback; + HermesDisconnectedCallback m_disconnectedCallback; + HermesTraceCallback m_traceCallback; +}; -**Send messages to the Upstream:** +// Inside m_getConfigurationCallback, call: +SignalHermesCurrentConfiguration(pSvc, sessionId, const HermesCurrentConfigurationData*); +// Inside m_setConfigurationCallback, call: +SignalHermesConfigurationNotification(pSvc, sessionId, const HermesNotificationData*); // on error only -```cpp -// Must call from inside Post() or from within a callback — both are on the network thread -ds.Post([&ds, sessionId]() { - BoardAvailableData board; - board.m_boardId = "BOARD-001"; - board.m_boardIdCreatedBy = "MyMachine"; - board.m_failedBoard = EBoardQuality::eGOOD; - board.m_flippedBoard = EFlippedBoard::eTOP_SIDE_IS_UP; - ds.Signal(sessionId, board); -}); +EnableHermesConfigurationService(pSvc, const HermesConfigurationServiceSettings*); +RunHermesConfigurationService(pSvc); // blocks +StopHermesConfigurationService(pSvc); +DeleteHermesConfigurationService(pSvc); ``` -### Modern::Upstream +### 3.7 Vertical service API (machine side) -```cpp -Hermes::Modern::Upstream us(unsigned laneId); -``` +The vertical channel connects a machine to a supervisory system (MES/ERP). -**Register callbacks:** - -```cpp -us.RegisterConnectedCallback( [](unsigned sessionId, const ConnectionInfo& info) { ... }); -us.RegisterDisconnectedCallback([](unsigned sessionId, const Error& err) { ... }); -us.RegisterStateChangeCallback( [](unsigned sessionId, EState state) { ... }); -us.RegisterTraceCallback( [](unsigned sessionId, ETraceType type, StringView msg) { ... }); +```c +HermesVerticalService* pVs = CreateHermesVerticalService( + const HermesVerticalServiceCallbacks* +); +EnableHermesVerticalService(pVs, const HermesVerticalServiceSettings*); +RunHermesVerticalService(pVs); + +// Signals: +SignalHermesVerticalServiceDescription(pVs, sessionId, const HermesSupervisoryServiceDescriptionData*); +SignalHermesQueryWorkOrderInfo(pVs, sessionId, const HermesQueryWorkOrderInfoData*); +SignalHermesReplyWorkOrderInfo(pVs, sessionId, const HermesReplyWorkOrderInfoData*); +SignalHermesVerticalServiceNotification(pVs, sessionId, const HermesNotificationData*); +SignalHermesVerticalServiceCheckAlive(pVs, sessionId, const HermesCheckAliveData*); +SignalHermesVerticalCurrentConfiguration(pVs, sessionId, const HermesCurrentConfigurationData*); +SignalHermesSendHermesCapabilities(pVs, sessionId, const HermesSendHermesCapabilitiesData*); + +// sessionId == 0 → broadcast to all clients with FeatureBoardTracking +SignalHermesBoardArrived(pVs, sessionId, const HermesBoardArrivedData*); +SignalHermesBoardDeparted(pVs, sessionId, const HermesBoardDepartedData*); + +ResetHermesVerticalServiceSession(pVs, sessionId, const HermesNotificationData*); +StopHermesVerticalService(pVs); +DeleteHermesVerticalService(pVs); +``` -// Messages received FROM the Downstream machine -us.RegisterServiceDescriptionCallback( [](unsigned sessionId, EState, const ServiceDescriptionData& d) { ... }); -us.RegisterBoardAvailableCallback( [](unsigned sessionId, EState, const BoardAvailableData& d) { ... }); -us.RegisterRevokeBoardAvailableCallback( [](unsigned sessionId, EState, const RevokeBoardAvailableData& d) { ... }); -us.RegisterTransportFinishedCallback( [](unsigned sessionId, EState, const TransportFinishedData& d) { ... }); -us.RegisterBoardForecastCallback( [](unsigned sessionId, EState, const BoardForecastData& d) { ... }); -us.RegisterSendBoardInfoCallback( [](unsigned sessionId, const SendBoardInfoData& d) { ... }); +### 3.8 Vertical client API (supervisory system side) -// Auxiliary -us.RegisterNotificationCallback([](unsigned sessionId, const NotificationData& d) { ... }); -us.RegisterCheckAliveCallback( [](unsigned sessionId, const CheckAliveData& d) { ... }); -us.RegisterCommandCallback( [](unsigned sessionId, const CommandData& d) { ... }); +```c +HermesVerticalClient* pVc = CreateHermesVerticalClient( + const HermesVerticalClientCallbacks* +); +EnableHermesVerticalClient(pVc, const HermesVerticalClientSettings*); +RunHermesVerticalClient(pVc); + +// Signals: +SignalHermesVerticalClientDescription(pVc, sessionId, const HermesSupervisoryServiceDescriptionData*); +SignalHermesSendWorkOrderInfo(pVc, sessionId, const HermesSendWorkOrderInfoData*); +SignalHermesVerticalGetConfiguration(pVc, sessionId, const HermesGetConfigurationData*); +SignalHermesVerticalSetConfiguration(pVc, sessionId, const HermesSetConfigurationData*); +SignalHermesVerticalQueryHermesCapabilities(pVc, sessionId, const HermesQueryHermesCapabilitiesData*); +SignalHermesVerticalClientNotification(pVc, sessionId, const HermesNotificationData*); +SignalHermesVerticalClientCheckAlive(pVc, sessionId, const HermesCheckAliveData*); +ResetHermesVerticalClient(pVc, const HermesNotificationData*); +StopHermesVerticalClient(pVc); +DeleteHermesVerticalClient(pVc); ``` -**Lifecycle:** +--- -```cpp -UpstreamSettings settings; -settings.m_machineId = "MyMachine"; -settings.m_hostAddress = "192.168.1.2"; // IP of the Downstream machine -settings.m_port = 50100; +## 4. C++ API -us.Enable(settings); -// ... -us.Stop(); -``` +**Header:** `Hermes.hpp` (includes `HermesDataConversion.hpp` → `HermesData.hpp`) +Use this when you need full session control, `Post()`, raw XML, or are implementing a class that handles both Upstream and Downstream on the same object. -**Send messages to the Downstream:** +### 4.1 Hermes::Downstream ```cpp -us.Post([&us, sessionId]() { - MachineReadyData ready; - ready.m_failedBoard = EBoardQuality::eANY; - us.Signal(sessionId, ready); +// Constructor — takes laneId and a reference to your callback implementation +Hermes::Downstream downstream(unsigned laneId, Hermes::IDownstreamCallback& callback); + +// Lifecycle +downstream.Enable(const DownstreamSettings&); // start listening +downstream.Run(); // blocks until Stop() +downstream.Stop(); // shut down +// Destructor calls DeleteHermesDownstream automatically + +// Post a callable onto the Hermes network thread (thread-safe from any thread) +downstream.Post([&]() { + downstream.Signal(sessionId, boardAvailableData); }); -``` ---- - -## 7. Low-level C++ API +// Signals — call from within Post() or from a callback (both are on the network thread) +downstream.Signal(sessionId, const ServiceDescriptionData&); +downstream.Signal(sessionId, const BoardAvailableData&); +downstream.Signal(sessionId, const RevokeBoardAvailableData&); +downstream.Signal(sessionId, const TransportFinishedData&); +downstream.Signal(sessionId, const BoardForecastData&); +downstream.Signal(sessionId, const SendBoardInfoData&); +downstream.Signal(sessionId, const NotificationData&); +downstream.Signal(sessionId, const CheckAliveData&); +downstream.Signal(sessionId, const CommandData&); +downstream.Reset(const NotificationData&); +downstream.Disable(const NotificationData&); + +// Testing only +downstream.Signal(sessionId, StringView rawXml); +downstream.Reset(StringView rawXml); +``` -`Hermes.hpp` provides `Hermes::Downstream` and `Hermes::Upstream` with virtual callback interfaces. Use this when you need the session ID in every callback, raw XML signals, or multiple sessions. +### 4.2 IDownstreamCallback — pure virtual interface -### Implementing IDownstreamCallback +Inherit from this and implement every pure virtual method. ```cpp -struct MyDownstreamHandler : Hermes::IDownstreamCallback +struct MyDownstreamCallback : Hermes::IDownstreamCallback { - // Pure virtuals — must implement all of these: + // Connection events — all pure virtual void OnConnected(unsigned sessionId, EState, const ConnectionInfo&) override; void OnDisconnected(unsigned sessionId, EState, const Error&) override; void OnState(unsigned sessionId, EState) override; - void OnTrace(unsigned sessionId, ETraceType, StringView) override; + void OnTrace(unsigned sessionId, ETraceType, StringView trace) override; - // Messages from Upstream — pure virtual: + // Messages received FROM the Upstream machine — pure virtual: void On(unsigned sessionId, EState, const ServiceDescriptionData&) override; void On(unsigned sessionId, EState, const MachineReadyData&) override; void On(unsigned sessionId, EState, const RevokeMachineReadyData&) override; @@ -452,36 +435,49 @@ struct MyDownstreamHandler : Hermes::IDownstreamCallback void On(unsigned sessionId, const NotificationData&) override; void On(unsigned sessionId, const CommandData&) override; - // Optional — have default empty implementations: + // Optional — have default empty implementations (not pure virtual): void On(unsigned sessionId, const CheckAliveData&) override {} void On(unsigned sessionId, const QueryBoardInfoData&) override {} }; ``` -**Usage:** +### 4.3 Hermes::Upstream ```cpp -MyDownstreamHandler handler; -Hermes::Downstream downstream(1, handler); // lane 1 - -DownstreamSettings settings; -settings.m_machineId = "Machine-A"; -settings.m_port = 50100; -downstream.Enable(settings); -downstream.Run(); // blocks until Stop() is called from another thread +Hermes::Upstream upstream(unsigned laneId, Hermes::IUpstreamCallback& callback); + +upstream.Enable(const UpstreamSettings&); +upstream.Run(); +upstream.Stop(); + +upstream.Post([&]() { upstream.Signal(sessionId, machineReadyData); }); + +upstream.Signal(sessionId, const ServiceDescriptionData&); +upstream.Signal(sessionId, const MachineReadyData&); +upstream.Signal(sessionId, const RevokeMachineReadyData&); +upstream.Signal(sessionId, const StartTransportData&); +upstream.Signal(sessionId, const StopTransportData&); +upstream.Signal(sessionId, const QueryBoardInfoData&); +upstream.Signal(sessionId, const NotificationData&); +upstream.Signal(sessionId, const CheckAliveData&); +upstream.Signal(sessionId, const CommandData&); +upstream.Reset(const NotificationData&); +upstream.Disable(const NotificationData&); +upstream.Signal(sessionId, StringView rawXml); +upstream.Reset(StringView rawXml); ``` -### Implementing IUpstreamCallback +### 4.4 IUpstreamCallback — pure virtual interface ```cpp -struct MyUpstreamHandler : Hermes::IUpstreamCallback +struct MyUpstreamCallback : Hermes::IUpstreamCallback { void OnConnected(unsigned sessionId, EState, const ConnectionInfo&) override; void OnDisconnected(unsigned sessionId, EState, const Error&) override; void OnState(unsigned sessionId, EState) override; void OnTrace(unsigned sessionId, ETraceType, StringView) override; - // Messages from Downstream — pure virtual: + // Messages received FROM the Downstream machine — pure virtual: void On(unsigned sessionId, EState, const ServiceDescriptionData&) override; void On(unsigned sessionId, EState, const BoardAvailableData&) override; void On(unsigned sessionId, EState, const RevokeBoardAvailableData&) override; @@ -496,300 +492,520 @@ struct MyUpstreamHandler : Hermes::IUpstreamCallback }; ``` -### Sending signals from within callbacks +### 4.5 Configuration service C++ wrapper -Always use `Post()` to send signals — it schedules the send on the Hermes network thread safely: +```cpp +Hermes::ConfigurationService svc(Hermes::IConfigurationServiceCallback& callback); +svc.Enable(const ConfigurationServiceSettings&); +svc.Run(); +svc.Stop(); +svc.Post(callable); + +// Free functions for the client side: +auto [config, error] = Hermes::GetConfiguration("192.168.1.2", 10, nullptr); +Error err = Hermes::SetConfiguration("192.168.1.2", setConfigData, 10, nullptr, nullptr, nullptr); +``` + +### 4.6 Dual-role helper — UpstreamCallbackHelper / DownstreamCallbackHelper + +When one class implements both `IUpstreamCallback` and `IDownstreamCallback`, some `On()` method signatures clash. Use the helpers to disambiguate: ```cpp -void On(unsigned sessionId, EState, const MachineReadyData&) override +struct MyHandler + : Hermes::UpstreamCallbackHelper + , Hermes::DownstreamCallbackHelper { - // DO NOT call Signal() directly here in the virtual callback API. - // Use Post() to dispatch onto the network thread: - m_downstream->Post([this, sessionId]() { - BoardAvailableData board; - board.m_boardId = "B-001"; - board.m_boardIdCreatedBy = "MachineA"; - board.m_failedBoard = EBoardQuality::eGOOD; - board.m_flippedBoard = EFlippedBoard::eTOP_SIDE_IS_UP; - m_downstream->Signal(sessionId, board); - }); -} + // Upstream methods get "Upstream" prefix: + void OnUpstreamConnected(unsigned sessionId, EState, const ConnectionInfo&) override; + void OnUpstream(unsigned sessionId, EState, const ServiceDescriptionData&) override; + void OnUpstream(unsigned sessionId, const NotificationData&) override; + void OnUpstream(unsigned sessionId, const CheckAliveData&) override; + void OnUpstream(unsigned sessionId, const CommandData&) override; + void OnUpstreamState(unsigned sessionId, EState) override; + void OnUpstreamDisconnected(unsigned sessionId, EState, const Error&) override; + void OnUpstreamTrace(unsigned sessionId, ETraceType, StringView) override; + + // Downstream methods get "Downstream" prefix: + void OnDownstreamConnected(unsigned sessionId, EState, const ConnectionInfo&) override; + void OnDownstream(unsigned sessionId, EState, const ServiceDescriptionData&) override; + void OnDownstream(unsigned sessionId, const NotificationData&) override; + void OnDownstream(unsigned sessionId, const CheckAliveData&) override; + void OnDownstream(unsigned sessionId, const CommandData&) override; + void OnDownstreamState(unsigned sessionId, EState) override; + void OnDownstreamDisconnected(unsigned sessionId, EState, const Error&) override; + void OnDownstreamTrace(unsigned sessionId, ETraceType, StringView) override; + + // Unambiguous methods remain unchanged: + void On(unsigned sessionId, EState, const BoardAvailableData&) override; + void On(unsigned sessionId, EState, const MachineReadyData&) override; + // etc. +}; ``` -> In `Modern::Downstream` / `Modern::Upstream`, callbacks already run on the network thread, so you can call `Signal()` directly inside them. +--- + +## 5. Modern C++ API + +**Header:** `HermesModern.hpp` +**Namespace:** `Hermes::Modern` + +Uses `std::function` callbacks instead of virtual interfaces. Manages the network thread internally. Recommended for all new application code. + +### 5.1 Modern::Downstream + +```cpp +Hermes::Modern::Downstream ds(unsigned laneId); + +// Register callbacks before calling Enable() +ds.RegisterConnectedCallback( [](unsigned sessionId, const ConnectionInfo& info) { }); +ds.RegisterDisconnectedCallback([](unsigned sessionId, const Error& err) { }); +ds.RegisterStateChangeCallback( [](unsigned sessionId, EState state) { }); +ds.RegisterTraceCallback( [](unsigned sessionId, ETraceType, StringView msg) { }); + +// Messages received FROM the Upstream machine: +ds.RegisterServiceDescriptionCallback( [](unsigned sessionId, EState, const ServiceDescriptionData&) { }); +ds.RegisterMachineReadyCallback( [](unsigned sessionId, EState, const MachineReadyData&) { }); +ds.RegisterRevokeMachineReadyCallback( [](unsigned sessionId, EState, const RevokeMachineReadyData&) { }); +ds.RegisterStartTransportCallback( [](unsigned sessionId, EState, const StartTransportData&) { }); +ds.RegisterStopTransportCallback( [](unsigned sessionId, EState, const StopTransportData&) { }); +ds.RegisterQueryBoardInfoCallback( [](unsigned sessionId, const QueryBoardInfoData&) { }); + +// Auxiliary: +ds.RegisterNotificationCallback([](unsigned sessionId, const NotificationData&) { }); +ds.RegisterCheckAliveCallback( [](unsigned sessionId, const CheckAliveData&) { }); +ds.RegisterCommandCallback( [](unsigned sessionId, const CommandData&) { }); + +// Lifecycle +DownstreamSettings settings; +settings.m_machineId = "MyMachine"; +settings.m_port = 50100; +ds.Enable(settings); // starts listening, launches network thread +ds.Stop(); // blocks until network thread finishes. Safe to call multiple times. + +// Send messages TO the Upstream (call from inside Post() or from within a callback) +ds.Post([&ds, sessionId]() { + ds.Signal(sessionId, boardAvailableData); +}); +``` + +### 5.2 Modern::Upstream + +```cpp +Hermes::Modern::Upstream us(unsigned laneId); + +us.RegisterConnectedCallback( [](unsigned sessionId, const ConnectionInfo&) { }); +us.RegisterDisconnectedCallback([](unsigned sessionId, const Error&) { }); +us.RegisterStateChangeCallback( [](unsigned sessionId, EState) { }); +us.RegisterTraceCallback( [](unsigned sessionId, ETraceType, StringView) { }); + +// Messages received FROM the Downstream machine: +us.RegisterServiceDescriptionCallback( [](unsigned sessionId, EState, const ServiceDescriptionData&) { }); +us.RegisterBoardAvailableCallback( [](unsigned sessionId, EState, const BoardAvailableData&) { }); +us.RegisterRevokeBoardAvailableCallback( [](unsigned sessionId, EState, const RevokeBoardAvailableData&) { }); +us.RegisterTransportFinishedCallback( [](unsigned sessionId, EState, const TransportFinishedData&) { }); +us.RegisterBoardForecastCallback( [](unsigned sessionId, EState, const BoardForecastData&) { }); +us.RegisterSendBoardInfoCallback( [](unsigned sessionId, const SendBoardInfoData&) { }); +us.RegisterNotificationCallback( [](unsigned sessionId, const NotificationData&) { }); +us.RegisterCheckAliveCallback( [](unsigned sessionId, const CheckAliveData&) { }); +us.RegisterCommandCallback( [](unsigned sessionId, const CommandData&) { }); + +UpstreamSettings settings; +settings.m_machineId = "MyMachine"; +settings.m_hostAddress = "192.168.1.2"; // downstream machine IP +settings.m_port = 50100; +us.Enable(settings); +us.Stop(); + +us.Post([&us, sessionId]() { + us.Signal(sessionId, machineReadyData); +}); +``` --- -## 8. Serialization API +## 6. Serialization API -`HermesSerialization.hpp` lets you convert any data struct to XML and back. Useful for logging, testing, or debugging. +**C header:** `HermesSerialization.h` +**C++ header:** `HermesSerialization.hpp` + +### 6.1 C API + +```c +// Callback that receives the serialized XML string +struct HermesSerializationCallback { + void (*m_pCall)(void* m_pData, HermesStringView result); + void* m_pData; +}; + +// Serialize any message to XML: +HermesSerializeServiceDescription(const HermesServiceDescriptionData*, HermesSerializationCallback); +HermesSerializeBoardAvailable(const HermesBoardAvailableData*, HermesSerializationCallback); +HermesSerializeMachineReady(const HermesMachineReadyData*, HermesSerializationCallback); +HermesSerializeStartTransport(const HermesStartTransportData*, HermesSerializationCallback); +HermesSerializeStopTransport(const HermesStopTransportData*, HermesSerializationCallback); +HermesSerializeTransportFinished(const HermesTransportFinishedData*, HermesSerializationCallback); +HermesSerializeRevokeBoardAvailable(const HermesRevokeBoardAvailableData*, HermesSerializationCallback); +HermesSerializeRevokeMachineReady(const HermesRevokeMachineReadyData*, HermesSerializationCallback); +HermesSerializeBoardForecast(const HermesBoardForecastData*, HermesSerializationCallback); +HermesSerializeQueryBoardInfo(const HermesQueryBoardInfoData*, HermesSerializationCallback); +HermesSerializeSendBoardInfo(const HermesSendBoardInfoData*, HermesSerializationCallback); +HermesSerializeNotification(const HermesNotificationData*, HermesSerializationCallback); +HermesSerializeCheckAlive(const HermesCheckAliveData*, HermesSerializationCallback); +HermesSerializeGetConfiguration(const HermesGetConfigurationData*, HermesSerializationCallback); +HermesSerializeSetConfiguration(const HermesSetConfigurationData*, HermesSerializationCallback); +HermesSerializeCurrentConfiguration(const HermesCurrentConfigurationData*, HermesSerializationCallback); +HermesSerializeCommand(const HermesCommandData*, HermesSerializationCallback); +// Vertical: +HermesSerializeSupervisoryServiceDescription(...); +HermesSerializeBoardArrived(...); +HermesSerializeBoardDeparted(...); +HermesSerializeQueryWorkOrderInfo(...); +HermesSerializeSendWorkOrderInfo(...); +HermesSerializeReplyWorkOrderInfo(...); +HermesSerializeQueryHermesCapabilities(...); +HermesSerializeSendHermesCapabilities(...); + +// Deserialize — fills in whichever callback matches the XML message type +void HermesDeserialize(HermesStringView xml, const HermesDeserializationCallbacks*); +``` + +### 6.2 C++ API ```cpp #include "HermesSerialization.hpp" -// Serialize to XML string +// Serialize BoardAvailableData board; -board.m_boardId = "BOARD-001"; -board.m_boardIdCreatedBy = "Machine-A"; +board.m_boardId = "PCB-001"; +board.m_boardIdCreatedBy = "MachineA"; board.m_failedBoard = EBoardQuality::eGOOD; board.m_flippedBoard = EFlippedBoard::eTOP_SIDE_IS_UP; +board.m_optionalLengthInMM = 150.0; std::string xml = Hermes::ToXml(board); -// xml = "BOARD-001..." -// Deserialize from XML string +// Deserialize Hermes::Optional result = Hermes::FromXml(xml); if (result.has_value()) { - std::cout << result->m_boardId << "\n"; // "BOARD-001" + std::cout << result->m_boardId << "\n"; } ``` -All message types are supported: +`ToXml()` and `FromXml()` are available for all message types. -```cpp -Hermes::ToXml(const ServiceDescriptionData&) -Hermes::ToXml(const BoardAvailableData&) -Hermes::ToXml(const RevokeBoardAvailableData&) -Hermes::ToXml(const MachineReadyData&) -Hermes::ToXml(const RevokeMachineReadyData&) -Hermes::ToXml(const StartTransportData&) -Hermes::ToXml(const StopTransportData&) -Hermes::ToXml(const TransportFinishedData&) -Hermes::ToXml(const BoardForecastData&) -Hermes::ToXml(const QueryBoardInfoData&) -Hermes::ToXml(const SendBoardInfoData&) -Hermes::ToXml(const NotificationData&) -Hermes::ToXml(const CheckAliveData&) -Hermes::ToXml(const SetConfigurationData&) -Hermes::ToXml(const CurrentConfigurationData&) -Hermes::ToXml(const GetConfigurationData&) -Hermes::ToXml(const CommandData&) +--- -// Vertical: -Hermes::ToXml(const SupervisoryServiceDescriptionData&) -Hermes::ToXml(const BoardArrivedData&) -Hermes::ToXml(const BoardDepartedData&) -Hermes::ToXml(const QueryWorkOrderInfoData&) -Hermes::ToXml(const SendWorkOrderInfoData&) -Hermes::ToXml(const ReplyWorkOrderInfoData&) -Hermes::ToXml(const QueryHermesCapabilitiesData&) -Hermes::ToXml(const SendHermesCapabilitiesData&) -``` +## 7. Data types reference ---- +All types are in `HermesData.hpp` (C++) or `HermesData.h` (C). + +### Key enums -## 9. Configuration service +| C++ enum | Values | +|----------|--------| +| `EState` | `eNOT_CONNECTED`, `eSOCKET_CONNECTED`, `eSERVICE_DESCRIPTION_DOWNSTREAM`, `eNOT_AVAILABLE_NOT_READY`, `eBOARD_AVAILABLE`, `eMACHINE_READY`, `eAVAILABLE_AND_READY`, `eTRANSPORTING`, `eTRANSPORT_STOPPED`, `eTRANSPORT_FINISHED`, `eDISCONNECTED` | +| `ETraceType` | `eSENT`, `eRECEIVED`, `eDEBUG`, `eINFO`, `eWARNING`, `eERROR` | +| `EBoardQuality` | `eANY`, `eGOOD`, `eBAD` | +| `EFlippedBoard` | `eSIDE_UP_IS_UNKNOWN`, `eTOP_SIDE_IS_UP`, `eBOTTOM_SIDE_IS_UP` | +| `ETransferState` | `eUNKNOWN`, `eNOT_STARTED`, `eINCOMPLETE`, `eCOMPLETE` | +| `ENotificationCode` | `eUNSPECIFIC`, `ePROTOCOL_ERROR`, `eCONNECTION_REFUSED_BECAUSE_OF_ESTABLISHED_CONNECTION`, `eCONNECTION_RESET_BECAUSE_OF_CHANGED_CONFIGURATION`, `eCONFIGURATION_ERROR`, `eMACHINE_SHUTDOWN`, `eBOARD_FORECAST_ERROR` | +| `ESeverity` | `eUNKNOWN`, `eFATAL`, `eERROR`, `eWARNING`, `eINFO` | +| `EErrorCode` | `eSUCCESS`, `eIMPLEMENTATION_ERROR`, `ePEER_ERROR`, `eCLIENT_ERROR`, `eNETWORK_ERROR`, `eTIMEOUT` | +| `EVerticalState` | `eNOT_CONNECTED`, `eSOCKET_CONNECTED`, `eSUPERVISORY_SERVICE_DESCRIPTION`, `eCONNECTED`, `eDISCONNECTED` | -The configuration service allows a remote tool (e.g. a factory MES) to read and write the machine's lane configuration over TCP on port 1248. +### Key C++ structs (HermesData.hpp) ```cpp -#include "Hermes.hpp" +// ServiceDescriptionData — exchanged by both sides at connection start +struct ServiceDescriptionData { + std::string m_machineId; // required + uint32_t m_laneId; // required, 1-based + Optional m_optionalInterfaceId; + std::string m_version; + // SupportedFeatures omitted for brevity +}; -struct MyConfigHandler : Hermes::IConfigurationServiceCallback -{ - void OnConnected(unsigned sessionId, const ConnectionInfo& info) override - { - std::cout << "Config client connected: " << info.m_address << "\n"; - } +// BoardAvailableData — sent by Downstream to signal a board is ready +struct BoardAvailableData { + std::string m_boardId; // required + std::string m_boardIdCreatedBy; // required + EBoardQuality m_failedBoard; // required + EFlippedBoard m_flippedBoard; // required + Optional m_optionalProductTypeId; + Optional m_optionalTopBarcode; + Optional m_optionalBottomBarcode; + Optional m_optionalLengthInMM; + Optional m_optionalWidthInMM; + Optional m_optionalThicknessInMM; + Optional m_optionalConveyorSpeedInMMPerSecs; + Optional m_optionalTopClearanceHeightInMM; + Optional m_optionalBottomClearanceHeightInMM; + Optional m_optionalWeightInGrams; + Optional m_optionalWorkOrderId; + Optional m_optionalBatchId; + Optional m_optionalRoute; + Optional m_optionalAction; + SubBoards m_optionalSubBoards; // std::vector +}; - // Remote client reads our configuration - CurrentConfigurationData OnGetConfiguration(unsigned, const ConnectionInfo&) override - { - CurrentConfigurationData config; - config.m_optionalMachineId = "Machine-A"; +// MachineReadyData — sent by Upstream to signal it can receive a board +struct MachineReadyData { + EBoardQuality m_failedBoard; // required — what quality it accepts + Optional m_optionalForecastId; + Optional m_optionalBoardId; + Optional m_optionalProductTypeId; + Optional m_optionalFlippedBoard; + Optional m_optionalTopBarcode; + Optional m_optionalBottomBarcode; + Optional m_optionalLengthInMM; + Optional m_optionalWidthInMM; + Optional m_optionalThicknessInMM; + Optional m_optionalConveyorSpeedInMMPerSecs; + Optional m_optionalTopClearanceHeightInMM; + Optional m_optionalBottomClearanceHeightInMM; + Optional m_optionalWeightInGrams; + Optional m_optionalWorkOrderId; + Optional m_optionalBatchId; +}; - UpstreamConfiguration up; - up.m_upstreamLaneId = 1; - up.m_hostAddress = "192.168.1.2"; - up.m_port = 50100; - config.m_upstreamConfigurations.push_back(up); +// StartTransportData — Upstream tells Downstream to release the board +struct StartTransportData { + std::string m_boardId; // required + Optional m_optionalConveyorSpeedInMMPerSecs; // optional override +}; - return config; - } +// StopTransportData — Upstream confirms board received +struct StopTransportData { + ETransferState m_transferState; // eCOMPLETE, eINCOMPLETE, eNOT_STARTED + std::string m_boardId; +}; - // Remote client writes a new configuration — return Error{} for success - Error OnSetConfiguration(unsigned, const ConnectionInfo&, - const SetConfigurationData& newConfig) override - { - // Apply newConfig to your machine... - return Error{}; // success - } +// TransportFinishedData — Downstream confirms board released +struct TransportFinishedData { + ETransferState m_transferState; + std::string m_boardId; +}; - void OnDisconnected(unsigned, const Error&) override {} - void OnTrace(unsigned, ETraceType, StringView) override {} +// NotificationData — error or status from either side +struct NotificationData { + ENotificationCode m_notificationCode; + ESeverity m_severity; + std::string m_description; }; -// Usage: -MyConfigHandler handler; -Hermes::ConfigurationService service(handler); +// Error — returned in disconnect callbacks and configuration calls +struct Error { + EErrorCode m_code; // eSUCCESS = no error + std::string m_text; + explicit operator bool() const; // true if error +}; -ConfigurationServiceSettings settings; -settings.m_port = cCONFIG_PORT; // 1248 +// ConnectionInfo +struct ConnectionInfo { + std::string m_address; + uint16_t m_port; + std::string m_hostName; +}; + +// Settings +struct DownstreamSettings { + std::string m_machineId; + Optional m_optionalClientAddress; // restrict to one IP + uint16_t m_port{0}; // 0 = cBASE_PORT (50100) + double m_checkAlivePeriodInSeconds{60}; + double m_reconnectWaitTimeInSeconds{10}; + ECheckAliveResponseMode m_checkAliveResponseMode{ECheckAliveResponseMode::eAUTO}; + ECheckState m_checkState{ECheckState::eSEND_AND_RECEIVE}; +}; -service.Enable(settings); -service.Run(); // blocks +struct UpstreamSettings { + std::string m_machineId; + std::string m_hostAddress; // downstream machine IP — required + uint16_t m_port{0}; + double m_checkAlivePeriodInSeconds{60}; + double m_reconnectWaitTimeInSeconds{10}; + ECheckAliveResponseMode m_checkAliveResponseMode{ECheckAliveResponseMode::eAUTO}; + ECheckState m_checkState{ECheckState::eSEND_AND_RECEIVE}; +}; ``` -### Remote configuration client +--- -Query or set a machine's configuration from another process: +## 8. Error handling ```cpp -// Get configuration -auto [config, error] = Hermes::GetConfiguration("192.168.1.2", 10, nullptr); -if (!error) { - std::cout << "Machine: " << config.m_optionalMachineId.value_or("unknown") << "\n"; +void OnDisconnected(unsigned sessionId, EState, const Error& error) override +{ + if (error) { // operator bool() — true if not eSUCCESS + std::cerr << "Error " << error.m_code << ": " << error.m_text << "\n"; + // EErrorCode values: + // eSUCCESS — not an error + // eIMPLEMENTATION_ERROR — bug inside the Hermes library + // ePEER_ERROR — remote machine misbehaved + // eCLIENT_ERROR — your code called the API incorrectly + // eNETWORK_ERROR — TCP/IP problem + // eTIMEOUT — timeout exceeded + } } +``` -// Set configuration -SetConfigurationData newConfig; -newConfig.m_machineId = "Machine-A"; - -Error err = Hermes::SetConfiguration( - "192.168.1.2", // host - newConfig, // configuration to write - 10, // timeout seconds - nullptr, // optional: resulting config out - nullptr, // optional: notifications out - nullptr // optional: trace callback -); +Notification codes (`ENotificationCode`) are what the **protocol** sends to describe issues: + +```cpp +void On(unsigned sessionId, const NotificationData& n) override +{ + // n.m_notificationCode: + // eUNSPECIFIC + // ePROTOCOL_ERROR + // eCONNECTION_REFUSED_BECAUSE_OF_ESTABLISHED_CONNECTION + // eCONNECTION_RESET_BECAUSE_OF_CHANGED_CONFIGURATION + // eCONFIGURATION_ERROR + // eMACHINE_SHUTDOWN + // eBOARD_FORECAST_ERROR + + // n.m_severity: eFATAL, eERROR, eWARNING, eINFO + // n.m_description: human-readable string +} ``` --- -## 10. Vertical interface +## 9. Thread safety rules + +The Hermes library runs its own event loop thread internally. -The vertical interface connects a machine to a supervisory system (MES/ERP) on a separate TCP port. `VerticalService` is the machine side. `VerticalClient` is the supervisory system side. +**Safe to call from any thread:** +- `Enable()`, `Stop()` +- `Post(callable)` — schedules work onto the Hermes thread + +**Must only be called from the Hermes thread** (i.e. from within a callback or from a `Post()` lambda): +- All `Signal()` methods +- `Reset()`, `Disable()` + +**Trace callbacks are called from the Hermes thread** — your trace handler must be thread-safe if it writes shared state. + +**Pattern for correct signal dispatch:** ```cpp -struct MyVerticalHandler : Hermes::IVerticalServiceCallback +// Inside a callback (already on Hermes thread) — direct call is fine +void On(unsigned sessionId, EState, const MachineReadyData&) override { - void OnConnected(unsigned sessionId, EVerticalState, const ConnectionInfo&) override {} - - void On(unsigned sessionId, EVerticalState, - const SupervisoryServiceDescriptionData& data) override - { - std::cout << "Supervisor connected: " << data.m_systemId << "\n"; - } + BoardAvailableData board; + board.m_boardId = "PCB-001"; + board.m_boardIdCreatedBy = "MyMachine"; + board.m_failedBoard = EBoardQuality::eGOOD; + board.m_flippedBoard = EFlippedBoard::eTOP_SIDE_IS_UP; + m_downstream.Signal(sessionId, board); // safe — we're on the Hermes thread +} - void On(unsigned sessionId, const GetConfigurationData&, const ConnectionInfo&) override {} - void On(unsigned sessionId, const SetConfigurationData&, const ConnectionInfo&) override {} - void On(unsigned sessionId, const NotificationData&) override {} - void On(unsigned sessionId, const QueryHermesCapabilitiesData&) override {} - void OnDisconnected(unsigned sessionId, EVerticalState, const Error&) override {} - void OnTrace(unsigned sessionId, ETraceType, StringView) override {} -}; +// From your own thread — use Post() +void SendFromMyThread(unsigned sessionId) +{ + m_downstream.Post([this, sessionId]() { + BoardAvailableData board; + // ... fill board ... + m_downstream.Signal(sessionId, board); + }); +} ``` --- -## 11. Complete examples +## 10. Complete examples -### Example 1 — Downstream machine (listens, receives a board) +### Example A — Downstream machine (C++) ```cpp -#include "HermesModern.hpp" +#include "Hermes.hpp" #include #include -#include #include #include static std::atomic g_running{true}; -int main() +struct MyDownstream : Hermes::IDownstreamCallback { - std::signal(SIGINT, [](int) { g_running = false; }); - - Hermes::Modern::Downstream ds(1); // lane 1 - - // --- Connection events --- - ds.RegisterConnectedCallback([](unsigned sessionId, const Hermes::ConnectionInfo& info) { - std::cout << "[DS] Upstream connected from " << info.m_address << "\n"; - }); + Hermes::Downstream* m_ds = nullptr; - ds.RegisterDisconnectedCallback([](unsigned sessionId, const Hermes::Error& err) { - std::cout << "[DS] Upstream disconnected\n"; - }); - - ds.RegisterStateChangeCallback([](unsigned sessionId, Hermes::EState state) { + void OnConnected(unsigned sessionId, Hermes::EState, const Hermes::ConnectionInfo& info) override { + std::cout << "[DS] Connected: " << info.m_address << "\n"; + } + void OnDisconnected(unsigned sessionId, Hermes::EState, const Hermes::Error& err) override { + std::cout << "[DS] Disconnected\n"; + } + void OnState(unsigned, Hermes::EState state) override { std::cout << "[DS] State: " << state << "\n"; - }); - - // --- Receive ServiceDescription from Upstream, reply with ours --- - ds.RegisterServiceDescriptionCallback( - [&ds](unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) { - std::cout << "[DS] Received ServiceDescription from: " << data.m_machineId << "\n"; - - // Reply with our own ServiceDescription - ds.Post([&ds, sessionId]() { - Hermes::ServiceDescriptionData reply; - reply.m_machineId = "Machine-Downstream"; - reply.m_laneId = 1; - ds.Signal(sessionId, reply); - std::cout << "[DS] Sent ServiceDescription\n"; - }); + } + void OnTrace(unsigned, Hermes::ETraceType, Hermes::StringView) override {} + + void On(unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) override { + std::cout << "[DS] ServiceDescription from: " << data.m_machineId << "\n"; + m_ds->Post([this, sessionId]() { + Hermes::ServiceDescriptionData reply; + reply.m_machineId = "DownstreamMachine"; + reply.m_laneId = 1; + m_ds->Signal(sessionId, reply); }); + } - // --- When Upstream is MachineReady, send BoardAvailable --- - ds.RegisterMachineReadyCallback( - [&ds](unsigned sessionId, Hermes::EState, const Hermes::MachineReadyData&) { - std::cout << "[DS] Upstream is MachineReady — sending BoardAvailable\n"; - - ds.Post([&ds, sessionId]() { - Hermes::BoardAvailableData board; - board.m_boardId = "PCB-2024-001"; - board.m_boardIdCreatedBy = "Machine-Downstream"; - board.m_failedBoard = Hermes::EBoardQuality::eGOOD; - board.m_flippedBoard = Hermes::EFlippedBoard::eTOP_SIDE_IS_UP; - ds.Signal(sessionId, board); - }); + void On(unsigned sessionId, Hermes::EState, const Hermes::MachineReadyData&) override { + std::cout << "[DS] MachineReady — sending BoardAvailable\n"; + m_ds->Post([this, sessionId]() { + Hermes::BoardAvailableData board; + board.m_boardId = "PCB-001"; + board.m_boardIdCreatedBy = "DownstreamMachine"; + board.m_failedBoard = Hermes::EBoardQuality::eGOOD; + board.m_flippedBoard = Hermes::EFlippedBoard::eTOP_SIDE_IS_UP; + m_ds->Signal(sessionId, board); }); + } - // --- When Upstream starts transport, confirm when done --- - ds.RegisterStartTransportCallback( - [&ds](unsigned sessionId, Hermes::EState, const Hermes::StartTransportData& data) { - std::cout << "[DS] StartTransport for board: " << data.m_boardId << "\n"; - - // Simulate board moving - std::this_thread::sleep_for(std::chrono::seconds(1)); - - ds.Post([&ds, sessionId, boardId = data.m_boardId]() { - Hermes::TransportFinishedData finished; - finished.m_transferState = Hermes::ETransferState::eCOMPLETE; - finished.m_boardId = boardId; - ds.Signal(sessionId, finished); - std::cout << "[DS] TransportFinished sent\n"; - }); + void On(unsigned sessionId, Hermes::EState, const Hermes::StartTransportData& data) override { + std::cout << "[DS] StartTransport: " << data.m_boardId << "\n"; + m_ds->Post([this, sessionId, id = data.m_boardId]() { + Hermes::TransportFinishedData fin; + fin.m_transferState = Hermes::ETransferState::eCOMPLETE; + fin.m_boardId = id; + m_ds->Signal(sessionId, fin); }); + } - // --- Notifications --- - ds.RegisterNotificationCallback([](unsigned, const Hermes::NotificationData& n) { + void On(unsigned, Hermes::EState, const Hermes::RevokeMachineReadyData&) override {} + void On(unsigned, Hermes::EState, const Hermes::StopTransportData&) override {} + void On(unsigned, const Hermes::NotificationData& n) override { std::cout << "[DS] Notification: " << n.m_description << "\n"; - }); + } + void On(unsigned, const Hermes::CommandData&) override {} +}; - // --- Start --- - Hermes::DownstreamSettings settings; - settings.m_machineId = "Machine-Downstream"; - settings.m_port = 50100; - ds.Enable(settings); +int main() +{ + std::signal(SIGINT, [](int) { g_running = false; }); - std::cout << "[DS] Listening on port 50100. Ctrl+C to stop.\n"; - while (g_running) - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + MyDownstream cb; + Hermes::Downstream ds(1, cb); + cb.m_ds = &ds; + Hermes::DownstreamSettings s; + s.m_machineId = "DownstreamMachine"; + s.m_port = 50100; + ds.Enable(s); + + std::thread t([&ds]() { ds.Run(); }); + std::cout << "[DS] Listening on :50100\n"; + while (g_running) std::this_thread::sleep_for(std::chrono::milliseconds(100)); ds.Stop(); - return 0; + t.join(); } ``` -### Example 2 — Upstream machine (connects, sends MachineReady) +### Example B — Upstream machine (Modern C++) ```cpp #include "HermesModern.hpp" #include #include -#include #include #include @@ -799,33 +1015,21 @@ int main() { std::signal(SIGINT, [](int) { g_running = false; }); - Hermes::Modern::Upstream us(1); // lane 1 - - us.RegisterConnectedCallback([](unsigned sessionId, const Hermes::ConnectionInfo& info) { - std::cout << "[US] Connected to downstream at " << info.m_address << "\n"; - }); + Hermes::Modern::Upstream us(1); - us.RegisterDisconnectedCallback([](unsigned sessionId, const Hermes::Error& err) { - std::cout << "[US] Disconnected\n"; + us.RegisterConnectedCallback([](unsigned, const Hermes::ConnectionInfo& info) { + std::cout << "[US] Connected to " << info.m_address << "\n"; }); - us.RegisterStateChangeCallback([](unsigned sessionId, Hermes::EState state) { - std::cout << "[US] State: " << state << "\n"; - }); - - // --- Receive ServiceDescription, reply and send MachineReady --- us.RegisterServiceDescriptionCallback( [&us](unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) { - std::cout << "[US] Received ServiceDescription from: " << data.m_machineId << "\n"; - + std::cout << "[US] ServiceDescription from: " << data.m_machineId << "\n"; us.Post([&us, sessionId]() { - // Send our ServiceDescription Hermes::ServiceDescriptionData reply; - reply.m_machineId = "Machine-Upstream"; + reply.m_machineId = "UpstreamMachine"; reply.m_laneId = 1; us.Signal(sessionId, reply); - // Then send MachineReady Hermes::MachineReadyData ready; ready.m_failedBoard = Hermes::EBoardQuality::eANY; us.Signal(sessionId, ready); @@ -833,30 +1037,24 @@ int main() }); }); - // --- Receive BoardAvailable, trigger StartTransport --- us.RegisterBoardAvailableCallback( [&us](unsigned sessionId, Hermes::EState, const Hermes::BoardAvailableData& board) { std::cout << "[US] BoardAvailable: " << board.m_boardId << "\n"; - - us.Post([&us, sessionId, boardId = board.m_boardId]() { + us.Post([&us, sessionId, id = board.m_boardId]() { Hermes::StartTransportData start; - start.m_boardId = boardId; + start.m_boardId = id; us.Signal(sessionId, start); - std::cout << "[US] StartTransport sent\n"; }); }); - // --- Receive TransportFinished, send StopTransport --- us.RegisterTransportFinishedCallback( [&us](unsigned sessionId, Hermes::EState, const Hermes::TransportFinishedData& data) { - std::cout << "[US] TransportFinished for board: " << data.m_boardId << "\n"; - - us.Post([&us, sessionId, boardId = data.m_boardId]() { + std::cout << "[US] TransportFinished: " << data.m_boardId << " — transfer complete\n"; + us.Post([&us, sessionId, id = data.m_boardId]() { Hermes::StopTransportData stop; stop.m_transferState = Hermes::ETransferState::eCOMPLETE; - stop.m_boardId = boardId; + stop.m_boardId = id; us.Signal(sessionId, stop); - std::cout << "[US] StopTransport sent — full board transfer complete\n"; }); }); @@ -864,49 +1062,42 @@ int main() std::cout << "[US] Notification: " << n.m_description << "\n"; }); - // EDIT THIS — set the downstream machine's IP: - Hermes::UpstreamSettings settings; - settings.m_machineId = "Machine-Upstream"; - settings.m_hostAddress = "192.168.1.2"; // <-- downstream machine IP - settings.m_port = 50100; - us.Enable(settings); - - std::cout << "[US] Connecting to 192.168.1.2:50100. Ctrl+C to stop.\n"; - while (g_running) - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + Hermes::UpstreamSettings s; + s.m_machineId = "UpstreamMachine"; + s.m_hostAddress = "192.168.1.2"; // ← downstream machine IP + s.m_port = 50100; + us.Enable(s); + std::cout << "[US] Connecting to 192.168.1.2:50100\n"; + while (g_running) std::this_thread::sleep_for(std::chrono::milliseconds(100)); us.Stop(); - return 0; } ``` -### Example 3 — XML serialization and inspection +--- -```cpp -#include "HermesSerialization.hpp" -#include +## 11. Bugs fixed -int main() -{ - // Serialize - Hermes::BoardAvailableData board; - board.m_boardId = "PCB-001"; - board.m_boardIdCreatedBy = "Machine-A"; - board.m_failedBoard = Hermes::EBoardQuality::eGOOD; - board.m_flippedBoard = Hermes::EFlippedBoard::eTOP_SIDE_IS_UP; - board.m_length = 150.0; - board.m_width = 100.0; - - std::string xml = Hermes::ToXml(board); - std::cout << "XML:\n" << xml << "\n"; - - // Deserialize - auto result = Hermes::FromXml(xml); - if (result.has_value()) { - std::cout << "BoardId: " << result->m_boardId << "\n"; - if (result->m_length.has_value()) - std::cout << "Length: " << *result->m_length << "mm\n"; - } - return 0; -} -``` \ No newline at end of file +### HermesStringView.h +- Missing `#include ` — caused `size_t` to be undefined, breaking every dependent file. +- `m_size` field was absent from the struct — caused `HermesDataConversion.hpp` to fail on every string conversion. + +### HermesOptional.hpp +- Custom `Optional` replaced with `using Optional = std::optional`. C++17 is required, so `std::optional` is correct. +- Added `operator<<` for `std::optional` in `namespace Hermes` — required by internal `BuildString()` usage in `AsioServer.cpp` and other `.cpp` files that log `Optional` values. + +### HermesStringView.hpp +- Custom `StringView` replaced with `using StringView = std::string_view`. C++17 is required. + +### HermesDataConversion.hpp +- **`Converter2C` typo** — corrected to `Converter2C`. Was a hard link error. +- **`uint32_t` / `unsigned` redefinition** — on ARM (Raspberry Pi), `unsigned` and `uint32_t` are the same type. The separate `uint32_t` overload caused a redefinition error. Removed the duplicate overload — the `unsigned` overload covers both. +- **Wrong field names in `BoardAvailableData` converter** — used invented short names (`m_length`, `m_width`, `m_thickness`, `m_conveyorSpeed`, `m_topClearanceHeight`, `m_bottomClearanceHeight`, `m_weight`, `m_subBoards`). Corrected to actual field names: `m_optionalLengthInMM`, `m_optionalWidthInMM`, `m_optionalThicknessInMM`, `m_optionalConveyorSpeedInMMPerSecs`, `m_optionalTopClearanceHeightInMM`, `m_optionalBottomClearanceHeightInMM`, `m_optionalWeightInGrams`, `m_optionalSubBoards`. +- **Same wrong field names in `MachineReadyData` converter** — same fix applied. +- **Wrong field name in `StartTransportData` converter** — `m_conveyorSpeed` → `m_optionalConveyorSpeedInMMPerSecs`. + +### HermesModern.hpp +- `Modern::Downstream::InternalCallbackWrapper` overrode the wrong message set (board messages instead of machine-control messages). Fixed to match `IDownstreamCallback` exactly. +- `Modern::Upstream::InternalCallbackWrapper` had the same problem in reverse. Fixed to match `IUpstreamCallback` exactly. +- `Stop()` was not safe to call twice — fixed using `std::atomic::exchange`. +- Callback signatures were missing `sessionId` — restored to full signature. diff --git a/src/include/HermesDataConversion.hpp b/src/include/HermesDataConversion.hpp index 6f20745..ca1453d 100644 --- a/src/include/HermesDataConversion.hpp +++ b/src/include/HermesDataConversion.hpp @@ -819,4 +819,4 @@ namespace Hermes return result; } -} // namespace Hermes \ No newline at end of file +} // namespace Hermes diff --git a/src/include/HermesOptional.hpp b/src/include/HermesOptional.hpp index b7a5c1a..dffa37d 100644 --- a/src/include/HermesOptional.hpp +++ b/src/include/HermesOptional.hpp @@ -49,4 +49,4 @@ namespace Hermes s << ""; return s; } -} \ No newline at end of file +} From d8cac07c9dae86796603dfb915cd4ed741a6e5db Mon Sep 17 00:00:00 2001 From: sahil agarwal Date: Fri, 27 Mar 2026 14:25:53 +0530 Subject: [PATCH 06/14] Revert "fix" This reverts commit db9acc17becc8a56f7dca655bfed01a7a82c29ee. --- src/include/HermesDataConversion.hpp | 404 +++++++++++++-------------- src/include/HermesOptional.hpp | 18 +- 2 files changed, 201 insertions(+), 221 deletions(-) diff --git a/src/include/HermesDataConversion.hpp b/src/include/HermesDataConversion.hpp index ca1453d..505a9f1 100644 --- a/src/include/HermesDataConversion.hpp +++ b/src/include/HermesDataConversion.hpp @@ -23,26 +23,31 @@ limitations under the License. namespace Hermes { // Verify constants match between C and C++ headers - static_assert(cCONFIG_PORT == cHERMES_CONFIG_PORT, "config port mismatch"); - static_assert(cBASE_PORT == cHERMES_BASE_PORT, "base port mismatch"); - static_assert(cMAX_MESSAGE_SIZE == cHERMES_MAX_MESSAGE_SIZE, "max message size mismatch"); + static_assert(cCONFIG_PORT == cHERMES_CONFIG_PORT, ""); + static_assert(cBASE_PORT == cHERMES_BASE_PORT, ""); + static_assert(cMAX_MESSAGE_SIZE == cHERMES_MAX_MESSAGE_SIZE, ""); // ------------------------------------------------------------------------- // String conversions + // FIX: HermesStringView now has m_pData and m_size (fixed in HermesStringView.h) + // FIX: std::optional uses value_or and has_value instead of custom Optional // ------------------------------------------------------------------------- inline void CppToC(const std::string& data, HermesStringView& result) { result.m_pData = data.data(); result.m_size = data.size(); } + inline void CToCpp(HermesStringView data, std::string& result) { result.assign(data.m_pData, data.m_size); } + inline HermesStringView ToC(StringView data) { return { data.data(), data.size() }; } + inline StringView ToCpp(HermesStringView data) { return { data.m_pData, data.m_size }; @@ -50,26 +55,37 @@ namespace Hermes inline void CppToC(const Optional& data, HermesStringView& result) { - if (data.has_value()) { result.m_pData = data->data(); result.m_size = data->size(); } - else { result.m_pData = nullptr; result.m_size = 0; } + if (data.has_value()) + { + result.m_pData = data->data(); + result.m_size = data->size(); + } + else + { + result.m_pData = nullptr; + result.m_size = 0; + } } + inline void CToCpp(HermesStringView data, Optional& result) { - result = data.m_pData ? Optional(std::in_place, data.m_pData, data.m_size) - : std::nullopt; + if (data.m_pData) + result = std::string(data.m_pData, data.m_size); + else + result = std::nullopt; } // ------------------------------------------------------------------------- // Scalar pass-throughs - // FIX: removed separate uint32_t overload — on ARM/RPi, unsigned == uint32_t, - // causing a redefinition error. One overload covers both. // ------------------------------------------------------------------------- - inline void CppToC(double data, double& result) { result = data; } - inline void CToCpp(double data, double& result) { result = data; } + inline void CppToC(double data, double& result) { result = data; } + inline void CToCpp(double data, double& result) { result = data; } + inline void CppToC(unsigned data, unsigned& result) { result = data; } + inline void CToCpp(unsigned data, unsigned& result) { result = data; } inline void CppToC(uint16_t data, uint16_t& result) { result = data; } inline void CToCpp(uint16_t data, uint16_t& result) { result = data; } - inline void CppToC(unsigned data, unsigned& result) { result = data; } - inline void CToCpp(unsigned data, unsigned& result) { result = data; } + inline void CppToC(uint32_t data, uint32_t& result) { result = data; } + inline void CToCpp(uint32_t data, uint32_t& result) { result = data; } // ------------------------------------------------------------------------- // Optional pointer conversions @@ -77,7 +93,8 @@ namespace Hermes template void CToCpp(const CT* pData, CppT& result) { - if (pData) CToCpp(*pData, result); + if (!pData) return; + CToCpp(*pData, result); } template @@ -90,9 +107,18 @@ namespace Hermes void CToCpp(const CT* pData, Optional& result) { if (!pData) { result = std::nullopt; return; } - CppT v{}; - CToCpp(*pData, v); - result = std::move(v); + CppT value{}; + CToCpp(*pData, value); + result = std::move(value); + } + + template + void CppToC(const Optional& data, Optional& result) + { + if (!data.has_value()) { result = std::nullopt; return; } + CT c{}; + CppToC(*data, c); + result = std::move(c); } template @@ -113,6 +139,15 @@ namespace Hermes std::vector m_pointers; }; + template + void CppToC(const std::vector& data, VectorHolder& result) + { + auto sz = data.size(); + result.m_values.resize(sz, CT{}); + for (uint32_t i = 0; i < sz; ++i) + CppToC(data[i], result.m_values[i]); + } + template void CppToC(const std::vector& data, VectorHolder& intermediate, CVector& result) { @@ -124,7 +159,7 @@ namespace Hermes CppToC(data[i], intermediate.m_values[i]); intermediate.m_pointers[i] = &intermediate.m_values[i]; } - result.m_pData = sz == 0 ? nullptr : intermediate.m_pointers.data(); + result.m_pData = (sz == 0) ? nullptr : intermediate.m_pointers.data(); result.m_size = sz; } @@ -137,90 +172,89 @@ namespace Hermes } // ------------------------------------------------------------------------- - // Enum conversions + // Enum conversions — static_assert verifies C and C++ enums stay in sync // ------------------------------------------------------------------------- static_assert(size(EState()) == cHERMES_STATE_ENUM_SIZE, "enum mismatch"); - inline EHermesState ToC(EState d) { return static_cast(d); } - inline EState ToCpp(EHermesState d) { return static_cast(d); } + inline EHermesState ToC(EState data) { return static_cast(data); } + inline EState ToCpp(EHermesState data) { return static_cast(data); } static_assert(size(ETraceType()) == cHERMES_TRACE_TYPE_ENUM_SIZE, "enum mismatch"); - inline EHermesTraceType ToC(ETraceType d) { return static_cast(d); } - inline ETraceType ToCpp(EHermesTraceType d) { return static_cast(d); } + inline EHermesTraceType ToC(ETraceType data) { return static_cast(data); } + inline ETraceType ToCpp(EHermesTraceType data) { return static_cast(data); } static_assert(size(ECheckAliveType()) == cHERMES_CHECK_ALIVE_TYPE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckAliveType d, EHermesCheckAliveType& r) { r = static_cast(d); } - inline void CToCpp(EHermesCheckAliveType d, ECheckAliveType& r) { r = static_cast(d); } + inline void CppToC(ECheckAliveType data, EHermesCheckAliveType& r) { r = static_cast(data); } + inline void CToCpp(EHermesCheckAliveType data, ECheckAliveType& r) { r = static_cast(data); } static_assert(size(EBoardQuality()) == cHERMES_BOARD_QUALITY_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardQuality d, EHermesBoardQuality& r) { r = static_cast(d); } - inline void CToCpp(EHermesBoardQuality d, EBoardQuality& r) { r = static_cast(d); } + inline void CppToC(EBoardQuality data, EHermesBoardQuality& r) { r = static_cast(data); } + inline void CToCpp(EHermesBoardQuality data, EBoardQuality& r) { r = static_cast(data); } static_assert(size(EFlippedBoard()) == cHERMES_FLIPPED_BOARD_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EFlippedBoard d, EHermesFlippedBoard& r) { r = static_cast(d); } - inline void CToCpp(EHermesFlippedBoard d, EFlippedBoard& r) { r = static_cast(d); } + inline void CppToC(EFlippedBoard data, EHermesFlippedBoard& r) { r = static_cast(data); } + inline void CToCpp(EHermesFlippedBoard data, EFlippedBoard& r) { r = static_cast(data); } static_assert(size(ESubBoardState()) == cHERMES_SUB_BOARD_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ESubBoardState d, EHermesSubBoardState& r) { r = static_cast(d); } - inline void CToCpp(EHermesSubBoardState d, ESubBoardState& r) { r = static_cast(d); } + inline void CppToC(ESubBoardState data, EHermesSubBoardState& r) { r = static_cast(data); } + inline void CToCpp(EHermesSubBoardState data, ESubBoardState& r) { r = static_cast(data); } static_assert(size(ETransferState()) == cHERMES_TRANSFER_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ETransferState d, EHermesTransferState& r) { r = static_cast(d); } - inline void CToCpp(EHermesTransferState d, ETransferState& r) { r = static_cast(d); } + inline void CppToC(ETransferState data, EHermesTransferState& r) { r = static_cast(data); } + inline void CToCpp(EHermesTransferState data, ETransferState& r) { r = static_cast(data); } static_assert(size(ENotificationCode()) == cHERMES_NOTIFICATION_CODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ENotificationCode d, EHermesNotificationCode& r) { r = static_cast(d); } - inline void CToCpp(EHermesNotificationCode d, ENotificationCode& r) { r = static_cast(d); } + inline void CppToC(ENotificationCode data, EHermesNotificationCode& r) { r = static_cast(data); } + inline void CToCpp(EHermesNotificationCode data, ENotificationCode& r) { r = static_cast(data); } static_assert(size(ESeverity()) == cHERMES_SEVERITY_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ESeverity d, EHermesSeverity& r) { r = static_cast(d); } - inline void CToCpp(EHermesSeverity d, ESeverity& r) { r = static_cast(d); } + inline void CppToC(ESeverity data, EHermesSeverity& r) { r = static_cast(data); } + inline void CToCpp(EHermesSeverity data, ESeverity& r) { r = static_cast(data); } static_assert(size(ECheckState()) == cHERMES_CHECK_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckState d, EHermesCheckState& r) { r = static_cast(d); } - inline void CToCpp(EHermesCheckState d, ECheckState& r) { r = static_cast(d); } + inline void CppToC(ECheckState data, EHermesCheckState& r) { r = static_cast(data); } + inline void CToCpp(EHermesCheckState data, ECheckState& r) { r = static_cast(data); } static_assert(size(EErrorCode()) == cHERMES_ERROR_CODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EErrorCode d, EHermesErrorCode& r) { r = static_cast(d); } - inline void CToCpp(EHermesErrorCode d, EErrorCode& r) { r = static_cast(d); } + inline void CppToC(EErrorCode data, EHermesErrorCode& r) { r = static_cast(data); } + inline void CToCpp(EHermesErrorCode data, EErrorCode& r) { r = static_cast(data); } static_assert(size(ECheckAliveResponseMode()) == cHERMES_CHECK_ALIVE_RESPONSE_MODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckAliveResponseMode d, EHermesCheckAliveResponseMode& r) { r = static_cast(d); } - inline void CToCpp(EHermesCheckAliveResponseMode d, ECheckAliveResponseMode& r) { r = static_cast(d); } + inline void CppToC(ECheckAliveResponseMode data, EHermesCheckAliveResponseMode& r) { r = static_cast(data); } + inline void CToCpp(EHermesCheckAliveResponseMode data, ECheckAliveResponseMode& r) { r = static_cast(data); } static_assert(size(EBoardArrivedTransfer()) == cHERMES_BOARD_ARRIVED_TRANSFER_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardArrivedTransfer d, EHermesBoardArrivedTransfer& r) { r = static_cast(d); } - inline void CToCpp(EHermesBoardArrivedTransfer d, EBoardArrivedTransfer& r) { r = static_cast(d); } + inline void CppToC(EBoardArrivedTransfer data, EHermesBoardArrivedTransfer& r) { r = static_cast(data); } + inline void CToCpp(EHermesBoardArrivedTransfer data, EBoardArrivedTransfer& r) { r = static_cast(data); } static_assert(size(EBoardDepartedTransfer()) == cHERMES_BOARD_DEPARTED_TRANSFER_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardDepartedTransfer d, EHermesBoardDepartedTransfer& r) { r = static_cast(d); } - inline void CToCpp(EHermesBoardDepartedTransfer d, EBoardDepartedTransfer& r) { r = static_cast(d); } + inline void CppToC(EBoardDepartedTransfer data, EHermesBoardDepartedTransfer& r) { r = static_cast(data); } + inline void CToCpp(EHermesBoardDepartedTransfer data, EBoardDepartedTransfer& r) { r = static_cast(data); } static_assert(size(EReplyWorkOrderInfoStatus()) == cHERMES_REPLY_WORK_ORDER_INFO_STATUS_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EReplyWorkOrderInfoStatus d, EHermesReplyWorkOrderInfoStatus& r) { r = static_cast(d); } - inline void CToCpp(EHermesReplyWorkOrderInfoStatus d, EReplyWorkOrderInfoStatus& r) { r = static_cast(d); } + inline void CppToC(EReplyWorkOrderInfoStatus data, EHermesReplyWorkOrderInfoStatus& r) { r = static_cast(data); } + inline void CToCpp(EHermesReplyWorkOrderInfoStatus data, EReplyWorkOrderInfoStatus& r) { r = static_cast(data); } static_assert(size(EVerticalState()) == cHERMES_VERTICAL_STATE_ENUM_SIZE, "enum mismatch"); - inline EHermesVerticalState ToC(EVerticalState d) { return static_cast(d); } - inline EVerticalState ToCpp(EHermesVerticalState d) { return static_cast(d); } + inline EHermesVerticalState ToC(EVerticalState data) { return static_cast(data); } + inline EVerticalState ToCpp(EHermesVerticalState data) { return static_cast(data); } // ------------------------------------------------------------------------- - // Forward declarations + // Forward declarations — specialisations defined below // ------------------------------------------------------------------------- template struct Converter2C; template struct ConverterBase { ConverterBase() = default; - ConverterBase(const ConverterBase&) = delete; - ConverterBase(ConverterBase&&) = delete; + ConverterBase(const ConverterBase&) = delete; + ConverterBase(ConverterBase&&) = delete; ConverterBase& operator=(const ConverterBase&) = delete; ConverterBase& operator=(ConverterBase&&) = delete; + const T* CPointer() const { return &m_data; } T m_data{}; }; - // ------------------------------------------------------------------------- - // UpstreamConfiguration / DownstreamConfiguration - // ------------------------------------------------------------------------- + // UpstreamConfiguration inline void CppToC(const UpstreamConfiguration& data, HermesUpstreamConfiguration& result) { CppToC(data.m_upstreamLaneId, result.m_upstreamLaneId); @@ -236,6 +270,7 @@ namespace Hermes CToCpp(data.m_port, result.m_port); } + // DownstreamConfiguration inline void CppToC(const DownstreamConfiguration& data, HermesDownstreamConfiguration& result) { CppToC(data.m_downstreamLaneId, result.m_downstreamLaneId); @@ -251,18 +286,16 @@ namespace Hermes CToCpp(data.m_port, result.m_port); } - // ------------------------------------------------------------------------- // SetConfigurationData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const SetConfigurationData& data) { - CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_machineId, m_data.m_machineId); CppToC(data.m_optionalSupervisorySystemPort, m_data.m_pOptionalSupervisorySystemPort); - CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); - CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); + CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); + CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); } private: VectorHolder m_upstreamHolder; @@ -271,23 +304,19 @@ namespace Hermes inline SetConfigurationData ToCpp(const HermesSetConfigurationData& data) { SetConfigurationData result; - CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_machineId, result.m_machineId); CToCpp(data.m_pOptionalSupervisorySystemPort, result.m_optionalSupervisorySystemPort); - CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); - CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); + CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); + CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); return result; } - // ------------------------------------------------------------------------- // GetConfigurationData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const GetConfigurationData&) {} }; inline GetConfigurationData ToCpp(const HermesGetConfigurationData&) { return {}; } - // ------------------------------------------------------------------------- // CurrentConfigurationData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -295,8 +324,8 @@ namespace Hermes { CppToC(data.m_optionalMachineId, m_data.m_optionalMachineId); CppToC(data.m_optionalSupervisorySystemPort, m_data.m_pOptionalSupervisorySystemPort); - CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); - CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); + CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); + CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); } private: VectorHolder m_upstreamHolder; @@ -305,16 +334,14 @@ namespace Hermes inline CurrentConfigurationData ToCpp(const HermesCurrentConfigurationData& data) { CurrentConfigurationData result; - CToCpp(data.m_optionalMachineId, result.m_optionalMachineId); + CToCpp(data.m_optionalMachineId, result.m_optionalMachineId); CToCpp(data.m_pOptionalSupervisorySystemPort, result.m_optionalSupervisorySystemPort); - CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); - CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); + CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); + CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); return result; } - // ------------------------------------------------------------------------- // ConnectionInfo - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -334,10 +361,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- - // Error - // FIX: was Converter2C (typo) — corrected to Converter2C - // ------------------------------------------------------------------------- + // FIX: Was Converter2C — typo, must be Converter2C template<> struct Converter2C : ConverterBase { @@ -355,19 +379,17 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // CheckAliveData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const CheckAliveData& data) { - CppToC(data.m_optionalType, m_optType, m_data.m_pOptionalType); + CppToC(data.m_optionalType, m_optionalType, m_data.m_pOptionalType); CppToC(data.m_optionalId, m_data.m_optionalId); } private: - EHermesCheckAliveType m_optType{}; + EHermesCheckAliveType m_optionalType{}; }; inline CheckAliveData ToCpp(const HermesCheckAliveData& data) { @@ -377,9 +399,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // NotificationData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -399,19 +419,17 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // ServiceDescriptionData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const ServiceDescriptionData& data) { - CppToC(data.m_machineId, m_data.m_machineId); - CppToC(data.m_laneId, m_data.m_laneId); + CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_laneId, m_data.m_laneId); CppToC(data.m_optionalInterfaceId, m_data.m_optionalInterfaceId); CppToC(data.m_version, m_data.m_version); - m_data.m_pSupportedFeatures = nullptr; + m_data.m_pSupportedFeatures = nullptr; // features are optional, not mapped here } }; inline ServiceDescriptionData ToCpp(const HermesServiceDescriptionData& data) @@ -424,10 +442,13 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // SubBoard / SubBoards helper - // SubBoards is typedef'd as std::vector in HermesData.hpp - // ------------------------------------------------------------------------- + struct SubBoardsHolder + { + VectorHolder m_holder; + HermesSubBoards m_data{}; + }; + inline void CppToC(const SubBoard& data, HermesSubBoard& result) { CppToC(data.m_pos, result.m_pos); @@ -441,163 +462,141 @@ namespace Hermes CToCpp(data.m_st, result.m_st); } - struct SubBoardsHolder - { - VectorHolder m_holder; - HermesSubBoards m_data{}; - }; - inline void CppToC(const SubBoards& data, SubBoardsHolder& holder) + inline void CppToC(const std::vector& data, SubBoardsHolder& holder) { CppToC(data, holder.m_holder, holder.m_data); } - inline void CToCpp(const HermesSubBoards& data, SubBoards& result) + inline void CToCpp(const HermesSubBoards& data, std::vector& result) { CToCpp(data, result); } - // ------------------------------------------------------------------------- // BoardAvailableData - // FIX: field names corrected to match HermesData.hpp: - // m_length -> m_optionalLengthInMM - // m_width -> m_optionalWidthInMM - // etc. - // m_subBoards -> m_optionalSubBoards - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const BoardAvailableData& data) { - CppToC(data.m_boardId, m_data.m_boardId); - CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_flippedBoard, m_data.m_flippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_optionalLengthInMM, m_optLength, m_data.m_pOptionalLengthInMM); - CppToC(data.m_optionalWidthInMM, m_optWidth, m_data.m_pOptionalWidthInMM); - CppToC(data.m_optionalThicknessInMM, m_optThick, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_optionalTopClearanceHeightInMM, m_optTopCl, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_optionalBottomClearanceHeightInMM, m_optBotCl, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_optionalWeightInGrams, m_optWeight, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); - CppToC(data.m_optionalRoute, m_optRoute, m_data.m_pOptionalRoute); - CppToC(data.m_optionalAction, m_optAction, m_data.m_pOptionalAction); - CppToC(data.m_optionalSubBoards, m_subBoards); - m_data.m_optionalSubBoards = m_subBoards.m_data; + CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_flippedBoard, m_data.m_flippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_length, m_optLength, m_data.m_pOptionalLengthInMM); + CppToC(data.m_width, m_optWidth, m_data.m_pOptionalWidthInMM); + CppToC(data.m_thickness, m_optThick, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_topClearanceHeight, m_optTopClear, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_bottomClearanceHeight, m_optBotClear, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_weight, m_optWeight, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_optionalRoute, m_optRoute, m_data.m_pOptionalRoute); + CppToC(data.m_optionalAction, m_optAction, m_data.m_pOptionalAction); + CppToC(data.m_subBoards, m_subBoardsHolder); + m_data.m_optionalSubBoards = m_subBoardsHolder.m_data; } private: double m_optLength{}, m_optWidth{}, m_optThick{}, m_optSpeed{}; - double m_optTopCl{}, m_optBotCl{}, m_optWeight{}; + double m_optTopClear{}, m_optBotClear{}, m_optWeight{}; uint16_t m_optRoute{}, m_optAction{}; - SubBoardsHolder m_subBoards; + SubBoardsHolder m_subBoardsHolder; }; inline BoardAvailableData ToCpp(const HermesBoardAvailableData& data) { BoardAvailableData result; - CToCpp(data.m_boardId, result.m_boardId); - CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); - CToCpp(data.m_failedBoard, result.m_failedBoard); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_flippedBoard, result.m_flippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); - CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); - CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM,result.m_optionalBottomClearanceHeightInMM); - CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); + CToCpp(data.m_boardId, result.m_boardId); + CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); + CToCpp(data.m_failedBoard, result.m_failedBoard); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_flippedBoard, result.m_flippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_length); + CToCpp(data.m_pOptionalWidthInMM, result.m_width); + CToCpp(data.m_pOptionalThicknessInMM, result.m_thickness); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_topClearanceHeight); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_bottomClearanceHeight); + CToCpp(data.m_pOptionalWeightInGrams, result.m_weight); CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); CToCpp(data.m_pOptionalAction, result.m_optionalAction); - CToCpp(data.m_optionalSubBoards, result.m_optionalSubBoards); + CToCpp(data.m_optionalSubBoards, result.m_subBoards); return result; } - // ------------------------------------------------------------------------- // RevokeBoardAvailableData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const RevokeBoardAvailableData&) {} }; inline RevokeBoardAvailableData ToCpp(const HermesRevokeBoardAvailableData&) { return {}; } - // ------------------------------------------------------------------------- // MachineReadyData - // FIX: field names corrected (m_length -> m_optionalLengthInMM, etc.) - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const MachineReadyData& data) { - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalForecastId, m_data.m_optionalForecastId); - CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_optionalFlippedBoard, m_optFlipped, m_data.m_pOptionalFlippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_optionalLengthInMM, m_optLength, m_data.m_pOptionalLengthInMM); - CppToC(data.m_optionalWidthInMM, m_optWidth, m_data.m_pOptionalWidthInMM); - CppToC(data.m_optionalThicknessInMM, m_optThick, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_optionalTopClearanceHeightInMM, m_optTopCl, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_optionalBottomClearanceHeightInMM,m_optBotCl, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_optionalWeightInGrams, m_optWeight, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalForecastId, m_data.m_optionalForecastId); + CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_optionalFlippedBoard, m_optFlipped, m_data.m_pOptionalFlippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_length, m_optLength, m_data.m_pOptionalLengthInMM); + CppToC(data.m_width, m_optWidth, m_data.m_pOptionalWidthInMM); + CppToC(data.m_thickness, m_optThick, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_topClearanceHeight, m_optTopClear, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_bottomClearanceHeight, m_optBotClear, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_weight, m_optWeight, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); } private: EHermesFlippedBoard m_optFlipped{}; double m_optLength{}, m_optWidth{}, m_optThick{}, m_optSpeed{}; - double m_optTopCl{}, m_optBotCl{}, m_optWeight{}; + double m_optTopClear{}, m_optBotClear{}, m_optWeight{}; }; inline MachineReadyData ToCpp(const HermesMachineReadyData& data) { MachineReadyData result; - CToCpp(data.m_failedBoard, result.m_failedBoard); - CToCpp(data.m_optionalForecastId, result.m_optionalForecastId); - CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); - CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); - CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM,result.m_optionalBottomClearanceHeightInMM); - CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); - CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + CToCpp(data.m_failedBoard, result.m_failedBoard); + CToCpp(data.m_optionalForecastId, result.m_optionalForecastId); + CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_length); + CToCpp(data.m_pOptionalWidthInMM, result.m_width); + CToCpp(data.m_pOptionalThicknessInMM, result.m_thickness); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_topClearanceHeight); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM,result.m_bottomClearanceHeight); + CToCpp(data.m_pOptionalWeightInGrams, result.m_weight); + CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); return result; } - // ------------------------------------------------------------------------- // RevokeMachineReadyData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const RevokeMachineReadyData&) {} }; inline RevokeMachineReadyData ToCpp(const HermesRevokeMachineReadyData&) { return {}; } - // ------------------------------------------------------------------------- // StartTransportData - // FIX: field name corrected: m_conveyorSpeed -> m_optionalConveyorSpeedInMMPerSecs - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const StartTransportData& data) { - CppToC(data.m_boardId, m_data.m_boardId); - CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); } private: double m_optSpeed{}; @@ -606,13 +605,11 @@ namespace Hermes { StartTransportData result; CToCpp(data.m_boardId, result.m_boardId); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); return result; } - // ------------------------------------------------------------------------- // StopTransportData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -630,9 +627,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // TransportFinishedData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -650,9 +645,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // QueryBoardInfoData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -670,13 +663,14 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // CommandData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { - explicit Converter2C(const CommandData& data) { CppToC(data.m_command, m_data.m_command); } + explicit Converter2C(const CommandData& data) + { + CppToC(data.m_command, m_data.m_command); + } }; inline CommandData ToCpp(const HermesCommandData& data) { @@ -685,9 +679,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // UpstreamSettings - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -715,9 +707,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // DownstreamSettings - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -745,9 +735,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // ConfigurationServiceSettings - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -765,9 +753,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // VerticalServiceSettings - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -791,9 +777,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // VerticalClientSettings - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { diff --git a/src/include/HermesOptional.hpp b/src/include/HermesOptional.hpp index dffa37d..24508a7 100644 --- a/src/include/HermesOptional.hpp +++ b/src/include/HermesOptional.hpp @@ -16,15 +16,11 @@ limitations under the License. // Copyright (c) ASM Assembly Systems GmbH & Co. KG // -// FIX: Replaced the custom Optional class with std::optional. -// The library requires C++17 (enforced in CMakeLists.txt), so std::optional -// is always available. -// -// FIX: Added operator<< for std::optional in the Hermes namespace. -// The internal implementation files (AsioServer.cpp etc.) use BuildString() -// which calls operator<< on every argument. The old custom Optional had -// operator<< defined on it. std::optional does not. Adding it here restores -// that behaviour without modifying any internal .cpp files. +// FIX: The original file rolled its own Optional class to avoid C++11/Boost +// dependencies. Since this library now requires C++17 (enforced in CMakeLists), +// we use std::optional directly. This removes the custom implementation entirely +// and fixes the two-argument constructor mismatch that caused compile errors in +// HermesDataConversion.hpp (Optional{data.m_pData, data.m_size}). // #pragma once @@ -33,7 +29,7 @@ limitations under the License. namespace Hermes { - // Drop-in alias — all code using Hermes::Optional compiles unchanged. + // Drop-in alias — all code using Hermes::Optional continues to compile. template using Optional = std::optional; @@ -49,4 +45,4 @@ namespace Hermes s << ""; return s; } -} +} \ No newline at end of file From 34c6b046efdc61edbc1a5b8451aca5537a56f49e Mon Sep 17 00:00:00 2001 From: sahil agarwal Date: Fri, 27 Mar 2026 14:26:57 +0530 Subject: [PATCH 07/14] commit --- examples/rpi1_upstream.cpp | 4 +- examples/rpi2_downstream.cpp | 209 +++++++++++++++++++++++------------ 2 files changed, 138 insertions(+), 75 deletions(-) diff --git a/examples/rpi1_upstream.cpp b/examples/rpi1_upstream.cpp index 9af0473..4dbded9 100644 --- a/examples/rpi1_upstream.cpp +++ b/examples/rpi1_upstream.cpp @@ -80,11 +80,11 @@ int main() // EDIT THIS — set the downstream machine's IP: Hermes::UpstreamSettings settings; settings.m_machineId = "Machine-Upstream"; - settings.m_hostAddress = "192.168.1.2"; // <-- downstream machine IP + settings.m_hostAddress = "10.0.0.2"; // <-- downstream machine IP settings.m_port = 50100; us.Enable(settings); - std::cout << "[US] Connecting to 192.168.1.2:50100. Ctrl+C to stop.\n"; + std::cout << "[US] Connecting to :50100. Ctrl+C to stop.\n"; while (g_running) std::this_thread::sleep_for(std::chrono::milliseconds(100)); diff --git a/examples/rpi2_downstream.cpp b/examples/rpi2_downstream.cpp index 8f6f69f..0547f36 100644 --- a/examples/rpi2_downstream.cpp +++ b/examples/rpi2_downstream.cpp @@ -1,94 +1,157 @@ - -#include "HermesModern.hpp" +// ============================================================================= +// rpi2_downstream.cpp — RPi2: Downstream machine +// +// Build: +// g++ -std=c++17 -o rpi2_downstream rpi2_downstream.cpp \ +// -I/path/to/hermes/src/include \ +// -L/path/to/hermes/build -lhermes \ +// -lboost_system -lpthread +// +// Run: +// LD_LIBRARY_PATH=/path/to/hermes/build ./rpi2_downstream +// +// What it does: +// - Listens on port 50100 for an incoming connection from RPi1 +// - Receives ServiceDescription from RPi1 +// - Sends ServiceDescription back to RPi1 +// - Prints connection state changes to console +// ============================================================================= + +#include "Hermes.hpp" #include +#include #include #include #include #include -static std::atomic g_running{true}; +static const uint16_t HERMES_PORT = 50100; +static const std::string THIS_MACHINE_ID = "RPi2-Downstream"; -int main() +static std::atomic g_running{true}; +static unsigned g_activeSession{0}; + +// Forward declare so callback can reference it +Hermes::Downstream* g_downstream = nullptr; + +void signalHandler(int) { g_running = false; } + +// ----------------------------------------------------------------------- +// Downstream callback — receives messages FROM the Upstream (RPi1) +// ----------------------------------------------------------------------- +struct DownstreamCallback : Hermes::IDownstreamCallback { - std::signal(SIGINT, [](int) { g_running = false; }); + void OnConnected(unsigned sessionId, Hermes::EState state, + const Hermes::ConnectionInfo& info) override + { + g_activeSession = sessionId; + std::cout << "[RPi2] RPi1 connected" + << " address=" << info.m_address + << " port=" << info.m_port + << " session=" << sessionId + << "\n"; + } + + // RPi1 sends its ServiceDescription — we receive it here + void On(unsigned sessionId, Hermes::EState state, + const Hermes::ServiceDescriptionData& data) override + { + std::cout << "[RPi2] Received ServiceDescription from RPi1" + << " machineId=" << data.m_machineId + << " laneId=" << data.m_laneId + << "\n"; + + // Respond with our own ServiceDescription + if (g_downstream) + { + g_downstream->Post([sessionId]() { + Hermes::ServiceDescriptionData reply; + reply.m_machineId = THIS_MACHINE_ID; + reply.m_laneId = 1; - Hermes::Modern::Downstream ds(1); // lane 1 + std::cout << "[RPi2] Sending ServiceDescription back to RPi1\n"; + g_downstream->Signal(sessionId, reply); + std::cout << "[RPi2] ** Hermes connection established successfully **\n"; + }); + } + } + + // These are required by the interface — not used in this demo + void On(unsigned, Hermes::EState, const Hermes::MachineReadyData&) override {} + void On(unsigned, Hermes::EState, const Hermes::RevokeMachineReadyData&) override {} + void On(unsigned, Hermes::EState, const Hermes::StartTransportData&) override {} + void On(unsigned, Hermes::EState, const Hermes::StopTransportData&) override {} + + void On(unsigned sessionId, const Hermes::NotificationData& data) override + { + std::cout << "[RPi2] Notification from RPi1: " << data.m_description << "\n"; + } + + void On(unsigned, const Hermes::CheckAliveData&) override {} + + void On(unsigned, const Hermes::CommandData&) override {} + + void OnState(unsigned sessionId, Hermes::EState state) override + { + std::cout << "[RPi2] State changed: " << static_cast(state) << "\n"; + } + + void OnDisconnected(unsigned sessionId, Hermes::EState state, + const Hermes::Error& error) override + { + std::cout << "[RPi2] RPi1 disconnected"; + if (error) + std::cout << " reason=" << error.m_text; + std::cout << "\n"; + g_activeSession = 0; + } + + void OnTrace(unsigned, Hermes::ETraceType, Hermes::StringView) override + { + // Uncomment to see full protocol trace: + // std::cout << "[RPi2][TRACE] " << std::string(trace) << "\n"; + } +}; - // --- Connection events --- - ds.RegisterConnectedCallback([](unsigned sessionId, const Hermes::ConnectionInfo& info) { - std::cout << "[DS] Upstream connected from " << info.m_address << "\n"; - }); +int main() +{ + std::signal(SIGINT, signalHandler); + std::signal(SIGTERM, signalHandler); - ds.RegisterDisconnectedCallback([](unsigned sessionId, const Hermes::Error& err) { - std::cout << "[DS] Upstream disconnected\n"; - }); + std::cout << "[RPi2] Starting Hermes Downstream\n"; + std::cout << "[RPi2] Listening on port " << HERMES_PORT + << " for RPi1...\n"; - ds.RegisterStateChangeCallback([](unsigned sessionId, Hermes::EState state) { - std::cout << "[DS] State: " << state << "\n"; - }); + DownstreamCallback callback; + Hermes::Downstream downstream(1, callback); // lane 1 + g_downstream = &downstream; - // --- Receive ServiceDescription from Upstream, reply with ours --- - ds.RegisterServiceDescriptionCallback( - [&ds](unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) { - std::cout << "[DS] Received ServiceDescription from: " << data.m_machineId << "\n"; + // Settings: who we are, and which port to listen on + // m_optionalClientAddress is left empty = accept from any IP + Hermes::DownstreamSettings settings; + settings.m_machineId = THIS_MACHINE_ID; + settings.m_port = HERMES_PORT; + settings.m_checkAlivePeriodInSeconds = 60.0; + settings.m_reconnectWaitTimeInSeconds = 5.0; - // Reply with our own ServiceDescription - ds.Post([&ds, sessionId]() { - Hermes::ServiceDescriptionData reply; - reply.m_machineId = "Machine-Downstream"; - reply.m_laneId = 1; - ds.Signal(sessionId, reply); - std::cout << "[DS] Sent ServiceDescription\n"; - }); - }); - - // --- When Upstream is MachineReady, send BoardAvailable --- - ds.RegisterMachineReadyCallback( - [&ds](unsigned sessionId, Hermes::EState, const Hermes::MachineReadyData&) { - std::cout << "[DS] Upstream is MachineReady — sending BoardAvailable\n"; - - ds.Post([&ds, sessionId]() { - Hermes::BoardAvailableData board; - board.m_boardId = "PCB-2024-001"; - board.m_boardIdCreatedBy = "Machine-Downstream"; - board.m_failedBoard = Hermes::EBoardQuality::eGOOD; - board.m_flippedBoard = Hermes::EFlippedBoard::eTOP_SIDE_IS_UP; - ds.Signal(sessionId, board); - }); - }); - - // --- When Upstream starts transport, confirm when done --- - ds.RegisterStartTransportCallback( - [&ds](unsigned sessionId, Hermes::EState, const Hermes::StartTransportData& data) { - std::cout << "[DS] StartTransport for board: " << data.m_boardId << "\n"; - - // Simulate board moving - std::this_thread::sleep_for(std::chrono::seconds(1)); - - ds.Post([&ds, sessionId, boardId = data.m_boardId]() { - Hermes::TransportFinishedData finished; - finished.m_transferState = Hermes::ETransferState::eCOMPLETE; - finished.m_boardId = boardId; - ds.Signal(sessionId, finished); - std::cout << "[DS] TransportFinished sent\n"; - }); - }); + downstream.Enable(settings); - // --- Notifications --- - ds.RegisterNotificationCallback([](unsigned, const Hermes::NotificationData& n) { - std::cout << "[DS] Notification: " << n.m_description << "\n"; + // Run Hermes event loop in a background thread + std::thread networkThread([&downstream]() { + downstream.Run(); }); - // --- Start --- - Hermes::DownstreamSettings settings; - settings.m_machineId = "Machine-Downstream"; - settings.m_port = 50100; - ds.Enable(settings); + std::cout << "[RPi2] Running. Press Ctrl+C to stop.\n"; - std::cout << "[DS] Listening on port 50100. Ctrl+C to stop.\n"; while (g_running) - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + std::cout << "[RPi2] Shutting down...\n"; + downstream.Stop(); + if (networkThread.joinable()) + networkThread.join(); - ds.Stop(); + g_downstream = nullptr; + std::cout << "[RPi2] Done.\n"; return 0; -} \ No newline at end of file +} From c466136a402fea998e47a72abf46878113e4b85e Mon Sep 17 00:00:00 2001 From: sahil agarwal Date: Fri, 27 Mar 2026 14:28:35 +0530 Subject: [PATCH 08/14] Revert "commit" This reverts commit 34c6b046efdc61edbc1a5b8451aca5537a56f49e. --- examples/rpi1_upstream.cpp | 4 +- examples/rpi2_downstream.cpp | 209 ++++++++++++----------------------- 2 files changed, 75 insertions(+), 138 deletions(-) diff --git a/examples/rpi1_upstream.cpp b/examples/rpi1_upstream.cpp index 4dbded9..9af0473 100644 --- a/examples/rpi1_upstream.cpp +++ b/examples/rpi1_upstream.cpp @@ -80,11 +80,11 @@ int main() // EDIT THIS — set the downstream machine's IP: Hermes::UpstreamSettings settings; settings.m_machineId = "Machine-Upstream"; - settings.m_hostAddress = "10.0.0.2"; // <-- downstream machine IP + settings.m_hostAddress = "192.168.1.2"; // <-- downstream machine IP settings.m_port = 50100; us.Enable(settings); - std::cout << "[US] Connecting to :50100. Ctrl+C to stop.\n"; + std::cout << "[US] Connecting to 192.168.1.2:50100. Ctrl+C to stop.\n"; while (g_running) std::this_thread::sleep_for(std::chrono::milliseconds(100)); diff --git a/examples/rpi2_downstream.cpp b/examples/rpi2_downstream.cpp index 0547f36..8f6f69f 100644 --- a/examples/rpi2_downstream.cpp +++ b/examples/rpi2_downstream.cpp @@ -1,157 +1,94 @@ -// ============================================================================= -// rpi2_downstream.cpp — RPi2: Downstream machine -// -// Build: -// g++ -std=c++17 -o rpi2_downstream rpi2_downstream.cpp \ -// -I/path/to/hermes/src/include \ -// -L/path/to/hermes/build -lhermes \ -// -lboost_system -lpthread -// -// Run: -// LD_LIBRARY_PATH=/path/to/hermes/build ./rpi2_downstream -// -// What it does: -// - Listens on port 50100 for an incoming connection from RPi1 -// - Receives ServiceDescription from RPi1 -// - Sends ServiceDescription back to RPi1 -// - Prints connection state changes to console -// ============================================================================= - -#include "Hermes.hpp" + +#include "HermesModern.hpp" #include -#include #include #include #include #include -static const uint16_t HERMES_PORT = 50100; -static const std::string THIS_MACHINE_ID = "RPi2-Downstream"; - -static std::atomic g_running{true}; -static unsigned g_activeSession{0}; - -// Forward declare so callback can reference it -Hermes::Downstream* g_downstream = nullptr; +static std::atomic g_running{true}; -void signalHandler(int) { g_running = false; } - -// ----------------------------------------------------------------------- -// Downstream callback — receives messages FROM the Upstream (RPi1) -// ----------------------------------------------------------------------- -struct DownstreamCallback : Hermes::IDownstreamCallback +int main() { - void OnConnected(unsigned sessionId, Hermes::EState state, - const Hermes::ConnectionInfo& info) override - { - g_activeSession = sessionId; - std::cout << "[RPi2] RPi1 connected" - << " address=" << info.m_address - << " port=" << info.m_port - << " session=" << sessionId - << "\n"; - } - - // RPi1 sends its ServiceDescription — we receive it here - void On(unsigned sessionId, Hermes::EState state, - const Hermes::ServiceDescriptionData& data) override - { - std::cout << "[RPi2] Received ServiceDescription from RPi1" - << " machineId=" << data.m_machineId - << " laneId=" << data.m_laneId - << "\n"; - - // Respond with our own ServiceDescription - if (g_downstream) - { - g_downstream->Post([sessionId]() { - Hermes::ServiceDescriptionData reply; - reply.m_machineId = THIS_MACHINE_ID; - reply.m_laneId = 1; + std::signal(SIGINT, [](int) { g_running = false; }); - std::cout << "[RPi2] Sending ServiceDescription back to RPi1\n"; - g_downstream->Signal(sessionId, reply); - std::cout << "[RPi2] ** Hermes connection established successfully **\n"; - }); - } - } - - // These are required by the interface — not used in this demo - void On(unsigned, Hermes::EState, const Hermes::MachineReadyData&) override {} - void On(unsigned, Hermes::EState, const Hermes::RevokeMachineReadyData&) override {} - void On(unsigned, Hermes::EState, const Hermes::StartTransportData&) override {} - void On(unsigned, Hermes::EState, const Hermes::StopTransportData&) override {} - - void On(unsigned sessionId, const Hermes::NotificationData& data) override - { - std::cout << "[RPi2] Notification from RPi1: " << data.m_description << "\n"; - } - - void On(unsigned, const Hermes::CheckAliveData&) override {} - - void On(unsigned, const Hermes::CommandData&) override {} - - void OnState(unsigned sessionId, Hermes::EState state) override - { - std::cout << "[RPi2] State changed: " << static_cast(state) << "\n"; - } - - void OnDisconnected(unsigned sessionId, Hermes::EState state, - const Hermes::Error& error) override - { - std::cout << "[RPi2] RPi1 disconnected"; - if (error) - std::cout << " reason=" << error.m_text; - std::cout << "\n"; - g_activeSession = 0; - } - - void OnTrace(unsigned, Hermes::ETraceType, Hermes::StringView) override - { - // Uncomment to see full protocol trace: - // std::cout << "[RPi2][TRACE] " << std::string(trace) << "\n"; - } -}; + Hermes::Modern::Downstream ds(1); // lane 1 -int main() -{ - std::signal(SIGINT, signalHandler); - std::signal(SIGTERM, signalHandler); + // --- Connection events --- + ds.RegisterConnectedCallback([](unsigned sessionId, const Hermes::ConnectionInfo& info) { + std::cout << "[DS] Upstream connected from " << info.m_address << "\n"; + }); - std::cout << "[RPi2] Starting Hermes Downstream\n"; - std::cout << "[RPi2] Listening on port " << HERMES_PORT - << " for RPi1...\n"; + ds.RegisterDisconnectedCallback([](unsigned sessionId, const Hermes::Error& err) { + std::cout << "[DS] Upstream disconnected\n"; + }); - DownstreamCallback callback; - Hermes::Downstream downstream(1, callback); // lane 1 - g_downstream = &downstream; + ds.RegisterStateChangeCallback([](unsigned sessionId, Hermes::EState state) { + std::cout << "[DS] State: " << state << "\n"; + }); - // Settings: who we are, and which port to listen on - // m_optionalClientAddress is left empty = accept from any IP - Hermes::DownstreamSettings settings; - settings.m_machineId = THIS_MACHINE_ID; - settings.m_port = HERMES_PORT; - settings.m_checkAlivePeriodInSeconds = 60.0; - settings.m_reconnectWaitTimeInSeconds = 5.0; + // --- Receive ServiceDescription from Upstream, reply with ours --- + ds.RegisterServiceDescriptionCallback( + [&ds](unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) { + std::cout << "[DS] Received ServiceDescription from: " << data.m_machineId << "\n"; - downstream.Enable(settings); + // Reply with our own ServiceDescription + ds.Post([&ds, sessionId]() { + Hermes::ServiceDescriptionData reply; + reply.m_machineId = "Machine-Downstream"; + reply.m_laneId = 1; + ds.Signal(sessionId, reply); + std::cout << "[DS] Sent ServiceDescription\n"; + }); + }); + + // --- When Upstream is MachineReady, send BoardAvailable --- + ds.RegisterMachineReadyCallback( + [&ds](unsigned sessionId, Hermes::EState, const Hermes::MachineReadyData&) { + std::cout << "[DS] Upstream is MachineReady — sending BoardAvailable\n"; + + ds.Post([&ds, sessionId]() { + Hermes::BoardAvailableData board; + board.m_boardId = "PCB-2024-001"; + board.m_boardIdCreatedBy = "Machine-Downstream"; + board.m_failedBoard = Hermes::EBoardQuality::eGOOD; + board.m_flippedBoard = Hermes::EFlippedBoard::eTOP_SIDE_IS_UP; + ds.Signal(sessionId, board); + }); + }); + + // --- When Upstream starts transport, confirm when done --- + ds.RegisterStartTransportCallback( + [&ds](unsigned sessionId, Hermes::EState, const Hermes::StartTransportData& data) { + std::cout << "[DS] StartTransport for board: " << data.m_boardId << "\n"; + + // Simulate board moving + std::this_thread::sleep_for(std::chrono::seconds(1)); + + ds.Post([&ds, sessionId, boardId = data.m_boardId]() { + Hermes::TransportFinishedData finished; + finished.m_transferState = Hermes::ETransferState::eCOMPLETE; + finished.m_boardId = boardId; + ds.Signal(sessionId, finished); + std::cout << "[DS] TransportFinished sent\n"; + }); + }); - // Run Hermes event loop in a background thread - std::thread networkThread([&downstream]() { - downstream.Run(); + // --- Notifications --- + ds.RegisterNotificationCallback([](unsigned, const Hermes::NotificationData& n) { + std::cout << "[DS] Notification: " << n.m_description << "\n"; }); - std::cout << "[RPi2] Running. Press Ctrl+C to stop.\n"; + // --- Start --- + Hermes::DownstreamSettings settings; + settings.m_machineId = "Machine-Downstream"; + settings.m_port = 50100; + ds.Enable(settings); + std::cout << "[DS] Listening on port 50100. Ctrl+C to stop.\n"; while (g_running) - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - - std::cout << "[RPi2] Shutting down...\n"; - downstream.Stop(); - if (networkThread.joinable()) - networkThread.join(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); - g_downstream = nullptr; - std::cout << "[RPi2] Done.\n"; + ds.Stop(); return 0; -} +} \ No newline at end of file From 53c418578c4da23e77f8df3fc53a1ecb2255388a Mon Sep 17 00:00:00 2001 From: sahil agarwal Date: Fri, 27 Mar 2026 14:28:35 +0530 Subject: [PATCH 09/14] Reapply "fix" This reverts commit d8cac07c9dae86796603dfb915cd4ed741a6e5db. --- src/include/HermesDataConversion.hpp | 404 ++++++++++++++------------- src/include/HermesOptional.hpp | 18 +- 2 files changed, 221 insertions(+), 201 deletions(-) diff --git a/src/include/HermesDataConversion.hpp b/src/include/HermesDataConversion.hpp index 505a9f1..ca1453d 100644 --- a/src/include/HermesDataConversion.hpp +++ b/src/include/HermesDataConversion.hpp @@ -23,31 +23,26 @@ limitations under the License. namespace Hermes { // Verify constants match between C and C++ headers - static_assert(cCONFIG_PORT == cHERMES_CONFIG_PORT, ""); - static_assert(cBASE_PORT == cHERMES_BASE_PORT, ""); - static_assert(cMAX_MESSAGE_SIZE == cHERMES_MAX_MESSAGE_SIZE, ""); + static_assert(cCONFIG_PORT == cHERMES_CONFIG_PORT, "config port mismatch"); + static_assert(cBASE_PORT == cHERMES_BASE_PORT, "base port mismatch"); + static_assert(cMAX_MESSAGE_SIZE == cHERMES_MAX_MESSAGE_SIZE, "max message size mismatch"); // ------------------------------------------------------------------------- // String conversions - // FIX: HermesStringView now has m_pData and m_size (fixed in HermesStringView.h) - // FIX: std::optional uses value_or and has_value instead of custom Optional // ------------------------------------------------------------------------- inline void CppToC(const std::string& data, HermesStringView& result) { result.m_pData = data.data(); result.m_size = data.size(); } - inline void CToCpp(HermesStringView data, std::string& result) { result.assign(data.m_pData, data.m_size); } - inline HermesStringView ToC(StringView data) { return { data.data(), data.size() }; } - inline StringView ToCpp(HermesStringView data) { return { data.m_pData, data.m_size }; @@ -55,37 +50,26 @@ namespace Hermes inline void CppToC(const Optional& data, HermesStringView& result) { - if (data.has_value()) - { - result.m_pData = data->data(); - result.m_size = data->size(); - } - else - { - result.m_pData = nullptr; - result.m_size = 0; - } + if (data.has_value()) { result.m_pData = data->data(); result.m_size = data->size(); } + else { result.m_pData = nullptr; result.m_size = 0; } } - inline void CToCpp(HermesStringView data, Optional& result) { - if (data.m_pData) - result = std::string(data.m_pData, data.m_size); - else - result = std::nullopt; + result = data.m_pData ? Optional(std::in_place, data.m_pData, data.m_size) + : std::nullopt; } // ------------------------------------------------------------------------- // Scalar pass-throughs + // FIX: removed separate uint32_t overload — on ARM/RPi, unsigned == uint32_t, + // causing a redefinition error. One overload covers both. // ------------------------------------------------------------------------- - inline void CppToC(double data, double& result) { result = data; } - inline void CToCpp(double data, double& result) { result = data; } - inline void CppToC(unsigned data, unsigned& result) { result = data; } - inline void CToCpp(unsigned data, unsigned& result) { result = data; } + inline void CppToC(double data, double& result) { result = data; } + inline void CToCpp(double data, double& result) { result = data; } inline void CppToC(uint16_t data, uint16_t& result) { result = data; } inline void CToCpp(uint16_t data, uint16_t& result) { result = data; } - inline void CppToC(uint32_t data, uint32_t& result) { result = data; } - inline void CToCpp(uint32_t data, uint32_t& result) { result = data; } + inline void CppToC(unsigned data, unsigned& result) { result = data; } + inline void CToCpp(unsigned data, unsigned& result) { result = data; } // ------------------------------------------------------------------------- // Optional pointer conversions @@ -93,8 +77,7 @@ namespace Hermes template void CToCpp(const CT* pData, CppT& result) { - if (!pData) return; - CToCpp(*pData, result); + if (pData) CToCpp(*pData, result); } template @@ -107,18 +90,9 @@ namespace Hermes void CToCpp(const CT* pData, Optional& result) { if (!pData) { result = std::nullopt; return; } - CppT value{}; - CToCpp(*pData, value); - result = std::move(value); - } - - template - void CppToC(const Optional& data, Optional& result) - { - if (!data.has_value()) { result = std::nullopt; return; } - CT c{}; - CppToC(*data, c); - result = std::move(c); + CppT v{}; + CToCpp(*pData, v); + result = std::move(v); } template @@ -139,15 +113,6 @@ namespace Hermes std::vector m_pointers; }; - template - void CppToC(const std::vector& data, VectorHolder& result) - { - auto sz = data.size(); - result.m_values.resize(sz, CT{}); - for (uint32_t i = 0; i < sz; ++i) - CppToC(data[i], result.m_values[i]); - } - template void CppToC(const std::vector& data, VectorHolder& intermediate, CVector& result) { @@ -159,7 +124,7 @@ namespace Hermes CppToC(data[i], intermediate.m_values[i]); intermediate.m_pointers[i] = &intermediate.m_values[i]; } - result.m_pData = (sz == 0) ? nullptr : intermediate.m_pointers.data(); + result.m_pData = sz == 0 ? nullptr : intermediate.m_pointers.data(); result.m_size = sz; } @@ -172,89 +137,90 @@ namespace Hermes } // ------------------------------------------------------------------------- - // Enum conversions — static_assert verifies C and C++ enums stay in sync + // Enum conversions // ------------------------------------------------------------------------- static_assert(size(EState()) == cHERMES_STATE_ENUM_SIZE, "enum mismatch"); - inline EHermesState ToC(EState data) { return static_cast(data); } - inline EState ToCpp(EHermesState data) { return static_cast(data); } + inline EHermesState ToC(EState d) { return static_cast(d); } + inline EState ToCpp(EHermesState d) { return static_cast(d); } static_assert(size(ETraceType()) == cHERMES_TRACE_TYPE_ENUM_SIZE, "enum mismatch"); - inline EHermesTraceType ToC(ETraceType data) { return static_cast(data); } - inline ETraceType ToCpp(EHermesTraceType data) { return static_cast(data); } + inline EHermesTraceType ToC(ETraceType d) { return static_cast(d); } + inline ETraceType ToCpp(EHermesTraceType d) { return static_cast(d); } static_assert(size(ECheckAliveType()) == cHERMES_CHECK_ALIVE_TYPE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckAliveType data, EHermesCheckAliveType& r) { r = static_cast(data); } - inline void CToCpp(EHermesCheckAliveType data, ECheckAliveType& r) { r = static_cast(data); } + inline void CppToC(ECheckAliveType d, EHermesCheckAliveType& r) { r = static_cast(d); } + inline void CToCpp(EHermesCheckAliveType d, ECheckAliveType& r) { r = static_cast(d); } static_assert(size(EBoardQuality()) == cHERMES_BOARD_QUALITY_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardQuality data, EHermesBoardQuality& r) { r = static_cast(data); } - inline void CToCpp(EHermesBoardQuality data, EBoardQuality& r) { r = static_cast(data); } + inline void CppToC(EBoardQuality d, EHermesBoardQuality& r) { r = static_cast(d); } + inline void CToCpp(EHermesBoardQuality d, EBoardQuality& r) { r = static_cast(d); } static_assert(size(EFlippedBoard()) == cHERMES_FLIPPED_BOARD_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EFlippedBoard data, EHermesFlippedBoard& r) { r = static_cast(data); } - inline void CToCpp(EHermesFlippedBoard data, EFlippedBoard& r) { r = static_cast(data); } + inline void CppToC(EFlippedBoard d, EHermesFlippedBoard& r) { r = static_cast(d); } + inline void CToCpp(EHermesFlippedBoard d, EFlippedBoard& r) { r = static_cast(d); } static_assert(size(ESubBoardState()) == cHERMES_SUB_BOARD_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ESubBoardState data, EHermesSubBoardState& r) { r = static_cast(data); } - inline void CToCpp(EHermesSubBoardState data, ESubBoardState& r) { r = static_cast(data); } + inline void CppToC(ESubBoardState d, EHermesSubBoardState& r) { r = static_cast(d); } + inline void CToCpp(EHermesSubBoardState d, ESubBoardState& r) { r = static_cast(d); } static_assert(size(ETransferState()) == cHERMES_TRANSFER_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ETransferState data, EHermesTransferState& r) { r = static_cast(data); } - inline void CToCpp(EHermesTransferState data, ETransferState& r) { r = static_cast(data); } + inline void CppToC(ETransferState d, EHermesTransferState& r) { r = static_cast(d); } + inline void CToCpp(EHermesTransferState d, ETransferState& r) { r = static_cast(d); } static_assert(size(ENotificationCode()) == cHERMES_NOTIFICATION_CODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ENotificationCode data, EHermesNotificationCode& r) { r = static_cast(data); } - inline void CToCpp(EHermesNotificationCode data, ENotificationCode& r) { r = static_cast(data); } + inline void CppToC(ENotificationCode d, EHermesNotificationCode& r) { r = static_cast(d); } + inline void CToCpp(EHermesNotificationCode d, ENotificationCode& r) { r = static_cast(d); } static_assert(size(ESeverity()) == cHERMES_SEVERITY_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ESeverity data, EHermesSeverity& r) { r = static_cast(data); } - inline void CToCpp(EHermesSeverity data, ESeverity& r) { r = static_cast(data); } + inline void CppToC(ESeverity d, EHermesSeverity& r) { r = static_cast(d); } + inline void CToCpp(EHermesSeverity d, ESeverity& r) { r = static_cast(d); } static_assert(size(ECheckState()) == cHERMES_CHECK_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckState data, EHermesCheckState& r) { r = static_cast(data); } - inline void CToCpp(EHermesCheckState data, ECheckState& r) { r = static_cast(data); } + inline void CppToC(ECheckState d, EHermesCheckState& r) { r = static_cast(d); } + inline void CToCpp(EHermesCheckState d, ECheckState& r) { r = static_cast(d); } static_assert(size(EErrorCode()) == cHERMES_ERROR_CODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EErrorCode data, EHermesErrorCode& r) { r = static_cast(data); } - inline void CToCpp(EHermesErrorCode data, EErrorCode& r) { r = static_cast(data); } + inline void CppToC(EErrorCode d, EHermesErrorCode& r) { r = static_cast(d); } + inline void CToCpp(EHermesErrorCode d, EErrorCode& r) { r = static_cast(d); } static_assert(size(ECheckAliveResponseMode()) == cHERMES_CHECK_ALIVE_RESPONSE_MODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckAliveResponseMode data, EHermesCheckAliveResponseMode& r) { r = static_cast(data); } - inline void CToCpp(EHermesCheckAliveResponseMode data, ECheckAliveResponseMode& r) { r = static_cast(data); } + inline void CppToC(ECheckAliveResponseMode d, EHermesCheckAliveResponseMode& r) { r = static_cast(d); } + inline void CToCpp(EHermesCheckAliveResponseMode d, ECheckAliveResponseMode& r) { r = static_cast(d); } static_assert(size(EBoardArrivedTransfer()) == cHERMES_BOARD_ARRIVED_TRANSFER_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardArrivedTransfer data, EHermesBoardArrivedTransfer& r) { r = static_cast(data); } - inline void CToCpp(EHermesBoardArrivedTransfer data, EBoardArrivedTransfer& r) { r = static_cast(data); } + inline void CppToC(EBoardArrivedTransfer d, EHermesBoardArrivedTransfer& r) { r = static_cast(d); } + inline void CToCpp(EHermesBoardArrivedTransfer d, EBoardArrivedTransfer& r) { r = static_cast(d); } static_assert(size(EBoardDepartedTransfer()) == cHERMES_BOARD_DEPARTED_TRANSFER_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardDepartedTransfer data, EHermesBoardDepartedTransfer& r) { r = static_cast(data); } - inline void CToCpp(EHermesBoardDepartedTransfer data, EBoardDepartedTransfer& r) { r = static_cast(data); } + inline void CppToC(EBoardDepartedTransfer d, EHermesBoardDepartedTransfer& r) { r = static_cast(d); } + inline void CToCpp(EHermesBoardDepartedTransfer d, EBoardDepartedTransfer& r) { r = static_cast(d); } static_assert(size(EReplyWorkOrderInfoStatus()) == cHERMES_REPLY_WORK_ORDER_INFO_STATUS_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EReplyWorkOrderInfoStatus data, EHermesReplyWorkOrderInfoStatus& r) { r = static_cast(data); } - inline void CToCpp(EHermesReplyWorkOrderInfoStatus data, EReplyWorkOrderInfoStatus& r) { r = static_cast(data); } + inline void CppToC(EReplyWorkOrderInfoStatus d, EHermesReplyWorkOrderInfoStatus& r) { r = static_cast(d); } + inline void CToCpp(EHermesReplyWorkOrderInfoStatus d, EReplyWorkOrderInfoStatus& r) { r = static_cast(d); } static_assert(size(EVerticalState()) == cHERMES_VERTICAL_STATE_ENUM_SIZE, "enum mismatch"); - inline EHermesVerticalState ToC(EVerticalState data) { return static_cast(data); } - inline EVerticalState ToCpp(EHermesVerticalState data) { return static_cast(data); } + inline EHermesVerticalState ToC(EVerticalState d) { return static_cast(d); } + inline EVerticalState ToCpp(EHermesVerticalState d) { return static_cast(d); } // ------------------------------------------------------------------------- - // Forward declarations — specialisations defined below + // Forward declarations // ------------------------------------------------------------------------- template struct Converter2C; template struct ConverterBase { ConverterBase() = default; - ConverterBase(const ConverterBase&) = delete; - ConverterBase(ConverterBase&&) = delete; + ConverterBase(const ConverterBase&) = delete; + ConverterBase(ConverterBase&&) = delete; ConverterBase& operator=(const ConverterBase&) = delete; ConverterBase& operator=(ConverterBase&&) = delete; - const T* CPointer() const { return &m_data; } T m_data{}; }; - // UpstreamConfiguration + // ------------------------------------------------------------------------- + // UpstreamConfiguration / DownstreamConfiguration + // ------------------------------------------------------------------------- inline void CppToC(const UpstreamConfiguration& data, HermesUpstreamConfiguration& result) { CppToC(data.m_upstreamLaneId, result.m_upstreamLaneId); @@ -270,7 +236,6 @@ namespace Hermes CToCpp(data.m_port, result.m_port); } - // DownstreamConfiguration inline void CppToC(const DownstreamConfiguration& data, HermesDownstreamConfiguration& result) { CppToC(data.m_downstreamLaneId, result.m_downstreamLaneId); @@ -286,16 +251,18 @@ namespace Hermes CToCpp(data.m_port, result.m_port); } + // ------------------------------------------------------------------------- // SetConfigurationData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const SetConfigurationData& data) { - CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_machineId, m_data.m_machineId); CppToC(data.m_optionalSupervisorySystemPort, m_data.m_pOptionalSupervisorySystemPort); - CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); - CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); + CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); + CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); } private: VectorHolder m_upstreamHolder; @@ -304,19 +271,23 @@ namespace Hermes inline SetConfigurationData ToCpp(const HermesSetConfigurationData& data) { SetConfigurationData result; - CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_machineId, result.m_machineId); CToCpp(data.m_pOptionalSupervisorySystemPort, result.m_optionalSupervisorySystemPort); - CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); - CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); + CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); + CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); return result; } + // ------------------------------------------------------------------------- // GetConfigurationData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const GetConfigurationData&) {} }; inline GetConfigurationData ToCpp(const HermesGetConfigurationData&) { return {}; } + // ------------------------------------------------------------------------- // CurrentConfigurationData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -324,8 +295,8 @@ namespace Hermes { CppToC(data.m_optionalMachineId, m_data.m_optionalMachineId); CppToC(data.m_optionalSupervisorySystemPort, m_data.m_pOptionalSupervisorySystemPort); - CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); - CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); + CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); + CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); } private: VectorHolder m_upstreamHolder; @@ -334,14 +305,16 @@ namespace Hermes inline CurrentConfigurationData ToCpp(const HermesCurrentConfigurationData& data) { CurrentConfigurationData result; - CToCpp(data.m_optionalMachineId, result.m_optionalMachineId); + CToCpp(data.m_optionalMachineId, result.m_optionalMachineId); CToCpp(data.m_pOptionalSupervisorySystemPort, result.m_optionalSupervisorySystemPort); - CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); - CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); + CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); + CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); return result; } + // ------------------------------------------------------------------------- // ConnectionInfo + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -361,7 +334,10 @@ namespace Hermes return result; } - // FIX: Was Converter2C — typo, must be Converter2C + // ------------------------------------------------------------------------- + // Error + // FIX: was Converter2C (typo) — corrected to Converter2C + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -379,17 +355,19 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // CheckAliveData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const CheckAliveData& data) { - CppToC(data.m_optionalType, m_optionalType, m_data.m_pOptionalType); + CppToC(data.m_optionalType, m_optType, m_data.m_pOptionalType); CppToC(data.m_optionalId, m_data.m_optionalId); } private: - EHermesCheckAliveType m_optionalType{}; + EHermesCheckAliveType m_optType{}; }; inline CheckAliveData ToCpp(const HermesCheckAliveData& data) { @@ -399,7 +377,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // NotificationData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -419,17 +399,19 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // ServiceDescriptionData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const ServiceDescriptionData& data) { - CppToC(data.m_machineId, m_data.m_machineId); - CppToC(data.m_laneId, m_data.m_laneId); + CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_laneId, m_data.m_laneId); CppToC(data.m_optionalInterfaceId, m_data.m_optionalInterfaceId); CppToC(data.m_version, m_data.m_version); - m_data.m_pSupportedFeatures = nullptr; // features are optional, not mapped here + m_data.m_pSupportedFeatures = nullptr; } }; inline ServiceDescriptionData ToCpp(const HermesServiceDescriptionData& data) @@ -442,13 +424,10 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // SubBoard / SubBoards helper - struct SubBoardsHolder - { - VectorHolder m_holder; - HermesSubBoards m_data{}; - }; - + // SubBoards is typedef'd as std::vector in HermesData.hpp + // ------------------------------------------------------------------------- inline void CppToC(const SubBoard& data, HermesSubBoard& result) { CppToC(data.m_pos, result.m_pos); @@ -462,141 +441,163 @@ namespace Hermes CToCpp(data.m_st, result.m_st); } - inline void CppToC(const std::vector& data, SubBoardsHolder& holder) + struct SubBoardsHolder + { + VectorHolder m_holder; + HermesSubBoards m_data{}; + }; + inline void CppToC(const SubBoards& data, SubBoardsHolder& holder) { CppToC(data, holder.m_holder, holder.m_data); } - inline void CToCpp(const HermesSubBoards& data, std::vector& result) + inline void CToCpp(const HermesSubBoards& data, SubBoards& result) { CToCpp(data, result); } + // ------------------------------------------------------------------------- // BoardAvailableData + // FIX: field names corrected to match HermesData.hpp: + // m_length -> m_optionalLengthInMM + // m_width -> m_optionalWidthInMM + // etc. + // m_subBoards -> m_optionalSubBoards + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const BoardAvailableData& data) { - CppToC(data.m_boardId, m_data.m_boardId); - CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_flippedBoard, m_data.m_flippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_length, m_optLength, m_data.m_pOptionalLengthInMM); - CppToC(data.m_width, m_optWidth, m_data.m_pOptionalWidthInMM); - CppToC(data.m_thickness, m_optThick, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_topClearanceHeight, m_optTopClear, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_bottomClearanceHeight, m_optBotClear, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_weight, m_optWeight, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); - CppToC(data.m_optionalRoute, m_optRoute, m_data.m_pOptionalRoute); - CppToC(data.m_optionalAction, m_optAction, m_data.m_pOptionalAction); - CppToC(data.m_subBoards, m_subBoardsHolder); - m_data.m_optionalSubBoards = m_subBoardsHolder.m_data; + CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_flippedBoard, m_data.m_flippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_optionalLengthInMM, m_optLength, m_data.m_pOptionalLengthInMM); + CppToC(data.m_optionalWidthInMM, m_optWidth, m_data.m_pOptionalWidthInMM); + CppToC(data.m_optionalThicknessInMM, m_optThick, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_optionalTopClearanceHeightInMM, m_optTopCl, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_optionalBottomClearanceHeightInMM, m_optBotCl, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_optionalWeightInGrams, m_optWeight, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_optionalRoute, m_optRoute, m_data.m_pOptionalRoute); + CppToC(data.m_optionalAction, m_optAction, m_data.m_pOptionalAction); + CppToC(data.m_optionalSubBoards, m_subBoards); + m_data.m_optionalSubBoards = m_subBoards.m_data; } private: double m_optLength{}, m_optWidth{}, m_optThick{}, m_optSpeed{}; - double m_optTopClear{}, m_optBotClear{}, m_optWeight{}; + double m_optTopCl{}, m_optBotCl{}, m_optWeight{}; uint16_t m_optRoute{}, m_optAction{}; - SubBoardsHolder m_subBoardsHolder; + SubBoardsHolder m_subBoards; }; inline BoardAvailableData ToCpp(const HermesBoardAvailableData& data) { BoardAvailableData result; - CToCpp(data.m_boardId, result.m_boardId); - CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); - CToCpp(data.m_failedBoard, result.m_failedBoard); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_flippedBoard, result.m_flippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_length); - CToCpp(data.m_pOptionalWidthInMM, result.m_width); - CToCpp(data.m_pOptionalThicknessInMM, result.m_thickness); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_topClearanceHeight); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_bottomClearanceHeight); - CToCpp(data.m_pOptionalWeightInGrams, result.m_weight); + CToCpp(data.m_boardId, result.m_boardId); + CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); + CToCpp(data.m_failedBoard, result.m_failedBoard); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_flippedBoard, result.m_flippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); + CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); + CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM,result.m_optionalBottomClearanceHeightInMM); + CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); CToCpp(data.m_pOptionalAction, result.m_optionalAction); - CToCpp(data.m_optionalSubBoards, result.m_subBoards); + CToCpp(data.m_optionalSubBoards, result.m_optionalSubBoards); return result; } + // ------------------------------------------------------------------------- // RevokeBoardAvailableData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const RevokeBoardAvailableData&) {} }; inline RevokeBoardAvailableData ToCpp(const HermesRevokeBoardAvailableData&) { return {}; } + // ------------------------------------------------------------------------- // MachineReadyData + // FIX: field names corrected (m_length -> m_optionalLengthInMM, etc.) + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const MachineReadyData& data) { - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalForecastId, m_data.m_optionalForecastId); - CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_optionalFlippedBoard, m_optFlipped, m_data.m_pOptionalFlippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_length, m_optLength, m_data.m_pOptionalLengthInMM); - CppToC(data.m_width, m_optWidth, m_data.m_pOptionalWidthInMM); - CppToC(data.m_thickness, m_optThick, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_topClearanceHeight, m_optTopClear, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_bottomClearanceHeight, m_optBotClear, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_weight, m_optWeight, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalForecastId, m_data.m_optionalForecastId); + CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_optionalFlippedBoard, m_optFlipped, m_data.m_pOptionalFlippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_optionalLengthInMM, m_optLength, m_data.m_pOptionalLengthInMM); + CppToC(data.m_optionalWidthInMM, m_optWidth, m_data.m_pOptionalWidthInMM); + CppToC(data.m_optionalThicknessInMM, m_optThick, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_optionalTopClearanceHeightInMM, m_optTopCl, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_optionalBottomClearanceHeightInMM,m_optBotCl, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_optionalWeightInGrams, m_optWeight, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); } private: EHermesFlippedBoard m_optFlipped{}; double m_optLength{}, m_optWidth{}, m_optThick{}, m_optSpeed{}; - double m_optTopClear{}, m_optBotClear{}, m_optWeight{}; + double m_optTopCl{}, m_optBotCl{}, m_optWeight{}; }; inline MachineReadyData ToCpp(const HermesMachineReadyData& data) { MachineReadyData result; - CToCpp(data.m_failedBoard, result.m_failedBoard); - CToCpp(data.m_optionalForecastId, result.m_optionalForecastId); - CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_length); - CToCpp(data.m_pOptionalWidthInMM, result.m_width); - CToCpp(data.m_pOptionalThicknessInMM, result.m_thickness); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_topClearanceHeight); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM,result.m_bottomClearanceHeight); - CToCpp(data.m_pOptionalWeightInGrams, result.m_weight); - CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + CToCpp(data.m_failedBoard, result.m_failedBoard); + CToCpp(data.m_optionalForecastId, result.m_optionalForecastId); + CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); + CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); + CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM,result.m_optionalBottomClearanceHeightInMM); + CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); + CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); return result; } + // ------------------------------------------------------------------------- // RevokeMachineReadyData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const RevokeMachineReadyData&) {} }; inline RevokeMachineReadyData ToCpp(const HermesRevokeMachineReadyData&) { return {}; } + // ------------------------------------------------------------------------- // StartTransportData + // FIX: field name corrected: m_conveyorSpeed -> m_optionalConveyorSpeedInMMPerSecs + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const StartTransportData& data) { - CppToC(data.m_boardId, m_data.m_boardId); - CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); } private: double m_optSpeed{}; @@ -605,11 +606,13 @@ namespace Hermes { StartTransportData result; CToCpp(data.m_boardId, result.m_boardId); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); return result; } + // ------------------------------------------------------------------------- // StopTransportData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -627,7 +630,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // TransportFinishedData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -645,7 +650,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // QueryBoardInfoData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -663,14 +670,13 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // CommandData + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { - explicit Converter2C(const CommandData& data) - { - CppToC(data.m_command, m_data.m_command); - } + explicit Converter2C(const CommandData& data) { CppToC(data.m_command, m_data.m_command); } }; inline CommandData ToCpp(const HermesCommandData& data) { @@ -679,7 +685,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // UpstreamSettings + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -707,7 +715,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // DownstreamSettings + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -735,7 +745,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // ConfigurationServiceSettings + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -753,7 +765,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // VerticalServiceSettings + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -777,7 +791,9 @@ namespace Hermes return result; } + // ------------------------------------------------------------------------- // VerticalClientSettings + // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { diff --git a/src/include/HermesOptional.hpp b/src/include/HermesOptional.hpp index 24508a7..dffa37d 100644 --- a/src/include/HermesOptional.hpp +++ b/src/include/HermesOptional.hpp @@ -16,11 +16,15 @@ limitations under the License. // Copyright (c) ASM Assembly Systems GmbH & Co. KG // -// FIX: The original file rolled its own Optional class to avoid C++11/Boost -// dependencies. Since this library now requires C++17 (enforced in CMakeLists), -// we use std::optional directly. This removes the custom implementation entirely -// and fixes the two-argument constructor mismatch that caused compile errors in -// HermesDataConversion.hpp (Optional{data.m_pData, data.m_size}). +// FIX: Replaced the custom Optional class with std::optional. +// The library requires C++17 (enforced in CMakeLists.txt), so std::optional +// is always available. +// +// FIX: Added operator<< for std::optional in the Hermes namespace. +// The internal implementation files (AsioServer.cpp etc.) use BuildString() +// which calls operator<< on every argument. The old custom Optional had +// operator<< defined on it. std::optional does not. Adding it here restores +// that behaviour without modifying any internal .cpp files. // #pragma once @@ -29,7 +33,7 @@ limitations under the License. namespace Hermes { - // Drop-in alias — all code using Hermes::Optional continues to compile. + // Drop-in alias — all code using Hermes::Optional compiles unchanged. template using Optional = std::optional; @@ -45,4 +49,4 @@ namespace Hermes s << ""; return s; } -} \ No newline at end of file +} From d1e22d4f8bb74668b9ca19c94a5f41f959e4f196 Mon Sep 17 00:00:00 2001 From: sahil agarwal Date: Fri, 27 Mar 2026 14:28:36 +0530 Subject: [PATCH 10/14] Revert "fix2" This reverts commit 5207d05f957aa2a9d5cc8f1fb0ae8b0140698c3e. --- README.md | 1401 +++++++++++--------------- src/include/HermesDataConversion.hpp | 2 +- src/include/HermesOptional.hpp | 2 +- 3 files changed, 607 insertions(+), 798 deletions(-) diff --git a/README.md b/README.md index ec6d0a1..d54faca 100644 --- a/README.md +++ b/README.md @@ -1,432 +1,449 @@ -# Hermes Standard Library — Complete API Reference +# Hermes C++ Library — Complete Reference -**Version:** 1.0 -**License:** Apache 2.0 — Copyright 2018 ASM Assembly Systems GmbH & Co. KG -**Protocol:** The Hermes Standard — vendor-independent machine-to-machine communication for SMT assembly +**Protocol:** The Hermes Standard — vendor-independent machine-to-machine communication for SMT assembly lines +**License:** Apache 2.0 **Requires:** C++17, Boost 1.66+, CMake 3.15+ --- ## Table of Contents -1. [Protocol primer](#1-protocol-primer) -2. [Building](#2-building) -3. [C API — Hermes.h](#3-c-api) -4. [C++ API — Hermes.hpp](#4-c-api-1) -5. [Modern C++ API — HermesModern.hpp](#5-modern-c-api) -6. [Serialization API — HermesSerialization.h/.hpp](#6-serialization-api) -7. [Data types reference — HermesData.h / HermesData.hpp](#7-data-types-reference) -8. [Error handling](#8-error-handling) -9. [Thread safety rules](#9-thread-safety-rules) -10. [Complete examples](#10-complete-examples) -11. [Bugs fixed in this release](#11-bugs-fixed) +1. [What is Hermes](#1-what-is-hermes) +2. [Architecture overview](#2-architecture-overview) +3. [Building the library](#3-building-the-library) +4. [Header map — what to include](#4-header-map) +5. [Core types reference](#5-core-types-reference) +6. [Modern C++ API — HermesModern.hpp](#6-modern-c-api) +7. [Low-level C++ API — Hermes.hpp](#7-low-level-c-api) +8. [Serialization API — HermesSerialization.hpp](#8-serialization-api) +9. [Configuration service](#9-configuration-service) +10. [Vertical interface](#10-vertical-interface) +11. [Complete examples](#11-complete-examples) +12. [Bugs fixed in this version](#12-bugs-fixed) --- -## 1. Protocol primer +## 1. What is Hermes -Hermes connects machines in a PCB assembly line over TCP/IP. Each machine has a **Downstream port** (server — listens for the previous machine) and an **Upstream port** (client — connects to the next machine). +Hermes is an open TCP/IP + XML protocol that connects machines in an electronics assembly line. Each machine has an **Upstream** port (faces the previous machine) and a **Downstream** port (faces the next machine). PCB boards flow from Upstream to Downstream along the lane. ``` -[Machine A]──Upstream──connects to──Downstream──[Machine B]──Upstream──connects to──Downstream──[Machine C] - port 50100 port 50101 +[Machine A] --downstream:50100--> [Machine B] --downstream:50101--> [Machine C] + <--upstream:50100--- <--upstream:50101--- ``` -**Session:** every TCP connect/disconnect cycle increments the session ID (starts at 1). All callbacks receive a `sessionId` so you can correlate events. +The Downstream machine **listens** on a port. The Upstream machine **connects** to it. So: -**Message flow for a board transfer:** +- `Hermes::Downstream` / `Modern::Downstream` — **server role**, listens for incoming connections +- `Hermes::Upstream` / `Modern::Upstream` — **client role**, connects to the downstream machine + +--- + +## 2. Architecture overview + +``` +Your application + | + |--- HermesModern.hpp (std::function callbacks — recommended) + |--- Hermes.hpp (virtual interface callbacks — advanced) + |--- HermesSerialization (XML serialize/deserialize) + | + +---> Hermes C API (Hermes.h / HermesData.h) + | compiled into libhermes.so + | + +---> Boost.Asio (networking) + +---> pugixml (XML parsing) +``` + +### Message flow — horizontal (machine to machine) ``` -Downstream (server) Upstream (client) - │◄──── TCP connect ──────────│ - │◄──── ServiceDescription ───│ Upstream identifies itself - │───── ServiceDescription ──►│ Downstream identifies itself - │◄──── MachineReady ─────────│ Upstream ready to receive - │───── BoardAvailable ───────►│ Downstream has a board - │◄──── StartTransport ────────│ Upstream says: send it - │ [board physically moves] │ - │───── TransportFinished ─────►│ Downstream: done - │◄──── StopTransport ─────────│ Upstream: confirmed received +Downstream machine Upstream machine +(Modern::Downstream) (Modern::Upstream) + | | + |<-- TCP connect -------------------- | + | | + |<-- ServiceDescription ------------- | (Upstream identifies itself) + |--> ServiceDescription -----------> | (Downstream identifies itself) + | | + |<-- MachineReady ------------------- | (Upstream ready to receive) + |--> BoardAvailable --------------> | (Downstream has a board) + | | + |<-- StartTransport ----------------- | (Upstream says: send it) + | [board physically moves] | + |--> TransportFinished -----------> | (Downstream confirms done) + | | + |<-- StopTransport ---------------- | (Upstream confirms received) ``` -**Who receives what:** +### Who receives what -| Role | Receives FROM peer | Sends TO peer | -|------|--------------------|---------------| -| Downstream (server) | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | -| Upstream (client) | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | +| You create | You listen for (receive) | You send | +|-------------------|-------------------------------------------------------------------|-------------------------------------------------------| +| `Modern::Downstream` | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | +| `Modern::Upstream` | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | --- -## 2. Building +## 3. Building the library + +### Prerequisites ```bash -# Install dependencies (Raspberry Pi OS / Debian / Ubuntu) +# Debian / Ubuntu / Raspberry Pi OS sudo apt install -y g++ cmake libboost-all-dev -# Build the library +# macOS +brew install cmake boost + +# Windows +# Install Boost via vcpkg: vcpkg install boost +``` + +### Build + +```bash git clone https://github.com/hermes-org/lib_cpp.git cd lib_cpp mkdir build && cd build cmake .. make -j4 +``` + +This produces `build/src/Hermes/libhermes.so` (Linux/macOS) or `hermes.dll` (Windows). -# Find the shared library -find . -name "libhermes.so" -# → ./src/Hermes/libhermes.so +### Compile your application -# Compile your application +```bash g++ -std=c++17 -o myapp myapp.cpp \ -I./src/include \ -L./build/src/Hermes -lhermes \ -lboost_system -lpthread -# Run +# Run (Linux — tell the linker where to find the .so) LD_LIBRARY_PATH=./build/src/Hermes ./myapp ``` --- -## 3. C API +## 4. Header map + +| Header | What it gives you | When to include it | +|--------|------------------|--------------------| +| `HermesModern.hpp` | `Modern::Downstream`, `Modern::Upstream` with std::function callbacks | **Start here. Recommended for all new code.** | +| `Hermes.hpp` | `Hermes::Downstream`, `Hermes::Upstream`, virtual callback interfaces | When you need session IDs, raw XML, or fine-grained control | +| `HermesData.hpp` | All C++ data structs, enums, settings structs | Included automatically by the above | +| `HermesSerialization.hpp` | `ToXml()`, `FromXml()` | When you need to inspect or log raw XML messages | +| `HermesOptional.hpp` | `Hermes::Optional` = `std::optional` | Included automatically | +| `HermesStringView.hpp` | `Hermes::StringView` = `std::string_view` | Included automatically | + +--- -**Header:** `Hermes.h` (includes `HermesData.h` and `HermesStringView.h`) -**Link:** `-lhermes` +## 5. Core types reference -The C API uses opaque handles and function-pointer callbacks. Every callback struct has the same shape: +### EState — connection state machine -```c -struct HermesXxxCallback { - void (*m_pCall)(void* m_pData, /* message params */); - void* m_pData; // passed back as first arg to m_pCall +```cpp +enum class EState { + eNOT_CONNECTED, // No TCP connection + eSOCKET_CONNECTED, // TCP connected, waiting for ServiceDescription + eSERVICE_DESCRIPTION_DOWNSTREAM, // ServiceDescription exchanged + eNOT_AVAILABLE_NOT_READY, // Connected, neither side ready + eBOARD_AVAILABLE, // Downstream has a board waiting + eMACHINE_READY, // Upstream is ready to receive + eAVAILABLE_AND_READY, // Both sides ready — transport can start + eTRANSPORTING, // Board is moving + eTRANSPORT_STOPPED, // Transport paused + eTRANSPORT_FINISHED, // Board delivered + eDISCONNECTED // Connection closed }; ``` -### 3.1 Constants +### ETraceType — log levels -```c -static const uint16_t cHERMES_BASE_PORT = 50100U; // default machine port -static const uint16_t cHERMES_CONFIG_PORT = 1248U; // configuration service port -static const unsigned cHERMES_MAX_MESSAGE_SIZE = 65536U; +```cpp +enum class ETraceType { + eSENT, // Raw XML sent over the wire + eRECEIVED, // Raw XML received + eDEBUG, + eINFO, + eWARNING, + eERROR +}; ``` -### 3.2 HermesStringView +### Error -Non-owning string (like `std::string_view`). All C structs use this. **Does not own the memory.** +```cpp +struct Error { + EErrorCode m_code; // eSUCCESS, eNETWORK_ERROR, eTIMEOUT, etc. + std::string m_text; // Human-readable description -```c -struct HermesStringView { - const char* m_pData; // NULL means "no string" (not even empty) - size_t m_size; + explicit operator bool() const; // true if error (code != eSUCCESS) }; ``` -### 3.3 Downstream API +### ConnectionInfo -**Lifecycle:** +```cpp +struct ConnectionInfo { + std::string m_address; // Remote IP address + uint16_t m_port; // Remote port + std::string m_hostName; // Remote hostname (if resolvable) +}; +``` -```c -// 1. Create -HermesDownstream* pDs = CreateHermesDownstream( - uint32_t laneId, // lane number (1-based) - const HermesDownstreamCallbacks* pCb // all callbacks -); +### ServiceDescriptionData -// 2. Enable (start listening) -HermesDownstreamSettings settings = {0}; -settings.m_machineId = {"MyMachine", 9}; -settings.m_port = 50100; -settings.m_checkAlivePeriodInSeconds = 60.0; -settings.m_reconnectWaitTimeInSeconds = 10.0; -EnableHermesDownstream(pDs, &settings); +Exchanged in both directions at connection start. Identifies the machine. -// 3. Run (blocks until Stop is called from another thread) -RunHermesDownstream(pDs); +```cpp +struct ServiceDescriptionData { + std::string m_machineId; // Required. Your machine identifier. + uint32_t m_laneId; // Required. Which lane (1-based). + Optional m_optionalInterfaceId; // Optional. Interface label. + std::string m_version; // Protocol version string. +}; +``` -// 4. Stop (call from another thread or from a PostHermesDownstream callback) -StopHermesDownstream(pDs); +### BoardAvailableData -// 5. Delete (after Run returns) -DeleteHermesDownstream(pDs); -``` +Sent by Downstream to signal a board is ready for transfer. -**Callbacks to register in `HermesDownstreamCallbacks`:** - -```c -struct HermesDownstreamCallbacks { - HermesConnectedCallback m_connectedCallback; // TCP connected - HermesServiceDescriptionCallback m_serviceDescriptionCallback; // upstream identified itself - HermesMachineReadyCallback m_machineReadyCallback; // upstream ready to receive - HermesRevokeMachineReadyCallback m_revokeMachineReadyCallback; // upstream cancels ready - HermesStartTransportCallback m_startTransportCallback; // upstream says: send board - HermesStopTransportCallback m_stopTransportCallback; // upstream confirms received - HermesQueryBoardInfoCallback m_queryBoardInfoCallback; // upstream asks board info - HermesNotificationCallback m_notificationCallback; // protocol notification - HermesStateCallback m_stateCallback; // state machine changed - HermesCheckAliveCallback m_checkAliveCallback; // keepalive ping/pong - HermesCommandCallback m_commandCallback; // vendor command - HermesDisconnectedCallback m_disconnectedCallback; // TCP disconnected - HermesTraceCallback m_traceCallback; // raw XML / debug log +```cpp +struct BoardAvailableData { + std::string m_boardId; // Required. Unique board identifier. + std::string m_boardIdCreatedBy; // Required. Machine that created the ID. + EBoardQuality m_failedBoard; // eANY, eGOOD, eBAD + Optional m_optionalProductTypeId; + EFlippedBoard m_flippedBoard; // eSIDE_UP_IS_UNKNOWN, eTOP_SIDE_IS_UP, eBOTTOM_SIDE_IS_UP + Optional m_optionalTopBarcode; + Optional m_optionalBottomBarcode; + Optional m_length; // mm + Optional m_width; // mm + Optional m_thickness; // mm + Optional m_conveyorSpeed; // mm/s + Optional m_topClearanceHeight; // mm + Optional m_bottomClearanceHeight; // mm + Optional m_weight; // grams + Optional m_optionalWorkOrderId; + Optional m_optionalBatchId; + Optional m_optionalRoute; + Optional m_optionalAction; + std::vector m_subBoards; }; ``` -**Signals (send to upstream):** +### MachineReadyData -```c -SignalHermesDownstreamServiceDescription(pDs, sessionId, const HermesServiceDescriptionData*); -SignalHermesBoardAvailable(pDs, sessionId, const HermesBoardAvailableData*); -SignalHermesRevokeBoardAvailable(pDs, sessionId, const HermesRevokeBoardAvailableData*); -SignalHermesTransportFinished(pDs, sessionId, const HermesTransportFinishedData*); -SignalHermesBoardForecast(pDs, sessionId, const HermesBoardForecastData*); -SignalHermesSendBoardInfo(pDs, sessionId, const HermesSendBoardInfoData*); -SignalHermesDownstreamNotification(pDs, sessionId, const HermesNotificationData*); -SignalHermesDownstreamCheckAlive(pDs, sessionId, const HermesCheckAliveData*); -SignalHermesDownstreamCommand(pDs, sessionId, const HermesCommandData*); +Sent by Upstream to signal it is ready to receive a board. -// Reset to initial state (sends notification then resets state machine) -ResetHermesDownstream(pDs, const HermesNotificationData*); +```cpp +struct MachineReadyData { + EBoardQuality m_failedBoard; // What quality board it can accept + Optional m_optionalForecastId; + Optional m_optionalBoardId; + Optional m_optionalProductTypeId; + Optional m_optionalFlippedBoard; + Optional m_optionalTopBarcode; + Optional m_optionalBottomBarcode; + Optional m_length; + Optional m_width; + Optional m_thickness; + Optional m_conveyorSpeed; + Optional m_topClearanceHeight; + Optional m_bottomClearanceHeight; + Optional m_weight; + Optional m_optionalWorkOrderId; + Optional m_optionalBatchId; +}; +``` -// Disable (stop accepting new connections, notify current peer) -DisableHermesDownstream(pDs, const HermesNotificationData*); +### StartTransportData / StopTransportData / TransportFinishedData -// Testing only — send raw XML string -SignalHermesDownstreamRawXml(pDs, sessionId, HermesStringView rawXml); -ResetHermesDownstreamRawXml(pDs, HermesStringView rawXml); -``` +```cpp +struct StartTransportData { + std::string m_boardId; // Which board to transport + Optional m_conveyorSpeed; // mm/s, optional override +}; -**Post a task onto the Hermes event thread (thread-safe):** +struct StopTransportData { + ETransferState m_transferState; // eCOMPLETE, eINCOMPLETE, eNOT_STARTED + std::string m_boardId; +}; -```c -struct HermesVoidCallback { - void (*m_pCall)(void* m_pData); - void* m_pData; +struct TransportFinishedData { + ETransferState m_transferState; + std::string m_boardId; }; -PostHermesDownstream(pDs, HermesVoidCallback); ``` -### 3.4 Upstream API +### NotificationData -**Lifecycle:** +Sent by either side to report errors or status. -```c -HermesUpstream* pUs = CreateHermesUpstream(laneId, const HermesUpstreamCallbacks*); +```cpp +struct NotificationData { + ENotificationCode m_notificationCode; // ePROTOCOL_ERROR, eMACHINE_SHUTDOWN, etc. + ESeverity m_severity; // eFATAL, eERROR, eWARNING, eINFO + std::string m_description; // Human-readable message +}; +``` -HermesUpstreamSettings settings = {0}; -settings.m_machineId = {"MyMachine", 9}; -settings.m_hostAddress = {"192.168.1.2", 11}; // downstream machine IP -settings.m_port = 50100; -settings.m_checkAlivePeriodInSeconds = 60.0; -settings.m_reconnectWaitTimeInSeconds = 10.0; -EnableHermesUpstream(pUs, &settings); +### Settings structs -RunHermesUpstream(pUs); // blocks -StopHermesUpstream(pUs); -DeleteHermesUpstream(pUs); -``` +```cpp +struct DownstreamSettings { + std::string m_machineId; // Required + Optional m_optionalClientAddress; // Restrict to one client IP + uint16_t m_port{0}; // 0 = use cBASE_PORT (50100) + double m_checkAlivePeriodInSeconds{60}; + double m_reconnectWaitTimeInSeconds{10}; + ECheckAliveResponseMode m_checkAliveResponseMode{eAUTO}; + ECheckState m_checkState{eSEND_AND_RECEIVE}; +}; -**Callbacks in `HermesUpstreamCallbacks`:** - -```c -struct HermesUpstreamCallbacks { - HermesConnectedCallback m_connectedCallback; - HermesServiceDescriptionCallback m_serviceDescriptionCallback; // downstream identified - HermesBoardAvailableCallback m_boardAvailableCallback; // downstream has board - HermesRevokeBoardAvailableCallback m_revokeBoardAvailableCallback; - HermesTransportFinishedCallback m_transportFinishedCallback; // downstream done - HermesBoardForecastCallback m_boardForecastCallback; // board coming soon - HermesSendBoardInfoCallback m_sendBoardInfoCallback; // board metadata - HermesNotificationCallback m_notificationCallback; - HermesStateCallback m_stateCallback; - HermesCheckAliveCallback m_checkAliveCallback; - HermesCommandCallback m_commandCallback; - HermesDisconnectedCallback m_disconnectedCallback; - HermesTraceCallback m_traceCallback; +struct UpstreamSettings { + std::string m_machineId; // Required + std::string m_hostAddress; // Required. IP of the downstream machine. + uint16_t m_port{0}; // 0 = use cBASE_PORT (50100) + double m_checkAlivePeriodInSeconds{60}; + double m_reconnectWaitTimeInSeconds{10}; + ECheckAliveResponseMode m_checkAliveResponseMode{eAUTO}; + ECheckState m_checkState{eSEND_AND_RECEIVE}; }; ``` -**Signals (send to downstream):** - -```c -SignalHermesUpstreamServiceDescription(pUs, sessionId, const HermesServiceDescriptionData*); -SignalHermesMachineReady(pUs, sessionId, const HermesMachineReadyData*); -SignalHermesRevokeMachineReady(pUs, sessionId, const HermesRevokeMachineReadyData*); -SignalHermesStartTransport(pUs, sessionId, const HermesStartTransportData*); -SignalHermesStopTransport(pUs, sessionId, const HermesStopTransportData*); -SignalHermesQueryBoardInfo(pUs, sessionId, const HermesQueryBoardInfoData*); -SignalHermesUpstreamNotification(pUs, sessionId, const HermesNotificationData*); -SignalHermesUpstreamCheckAlive(pUs, sessionId, const HermesCheckAliveData*); -SignalHermesUpstreamCommand(pUs, sessionId, const HermesCommandData*); -ResetHermesUpstream(pUs, const HermesNotificationData*); -DisableHermesUpstream(pUs, const HermesNotificationData*); -PostHermesUpstream(pUs, HermesVoidCallback); -``` +--- -### 3.5 Configuration client API +## 6. Modern C++ API -Get or set a machine's Hermes lane configuration over TCP (port 1248). +`HermesModern.hpp` provides `Modern::Downstream` and `Modern::Upstream`. These wrap the low-level virtual callback interface with `std::function` callbacks. This is the recommended API for new code. -```c -// Get configuration (synchronous, blocks until complete or timeout) -struct HermesGetConfigurationCallbacks { - HermesCurrentConfigurationCallback m_currentConfigurationCallback; - HermesErrorCallback m_errorCallback; - HermesTraceCallback m_traceCallback; -}; -GetHermesConfiguration( - HermesStringView hostName, // e.g. "192.168.1.2" - unsigned timeoutInSeconds, // e.g. 10 - const HermesGetConfigurationCallbacks* -); +### Modern::Downstream -// Set configuration (synchronous) -struct HermesSetConfigurationCallbacks { - HermesCurrentConfigurationCallback m_currentConfigurationCallback; - HermesErrorCallback m_errorCallback; - HermesNotificationCallback m_notificationCallback; - HermesTraceCallback m_traceCallback; -}; -SetHermesConfiguration( - HermesStringView hostName, - const HermesSetConfigurationData*, - unsigned timeoutInSeconds, - const HermesSetConfigurationCallbacks* -); +```cpp +Hermes::Modern::Downstream ds(unsigned laneId); ``` -### 3.6 Configuration service API - -Expose a configuration service on the machine (port 1248) so remote tools can query/set config. +**Register callbacks:** -```c -HermesConfigurationService* pSvc = CreateHermesConfigurationService( - const HermesConfigurationServiceCallbacks* -); +```cpp +// Connection events — sessionId identifies the active connection +ds.RegisterConnectedCallback( [](unsigned sessionId, const ConnectionInfo& info) { ... }); +ds.RegisterDisconnectedCallback([](unsigned sessionId, const Error& err) { ... }); +ds.RegisterStateChangeCallback( [](unsigned sessionId, EState state) { ... }); +ds.RegisterTraceCallback( [](unsigned sessionId, ETraceType type, StringView msg) { ... }); + +// Messages received FROM the Upstream machine +ds.RegisterServiceDescriptionCallback( [](unsigned sessionId, EState, const ServiceDescriptionData& d) { ... }); +ds.RegisterMachineReadyCallback( [](unsigned sessionId, EState, const MachineReadyData& d) { ... }); +ds.RegisterRevokeMachineReadyCallback( [](unsigned sessionId, EState, const RevokeMachineReadyData& d) { ... }); +ds.RegisterStartTransportCallback( [](unsigned sessionId, EState, const StartTransportData& d) { ... }); +ds.RegisterStopTransportCallback( [](unsigned sessionId, EState, const StopTransportData& d) { ... }); +ds.RegisterQueryBoardInfoCallback( [](unsigned sessionId, const QueryBoardInfoData& d) { ... }); + +// Auxiliary +ds.RegisterNotificationCallback([](unsigned sessionId, const NotificationData& d) { ... }); +ds.RegisterCheckAliveCallback( [](unsigned sessionId, const CheckAliveData& d) { ... }); +ds.RegisterCommandCallback( [](unsigned sessionId, const CommandData& d) { ... }); +``` -struct HermesConfigurationServiceCallbacks { - HermesConnectedCallback m_connectedCallback; - HermesSetConfigurationCallback m_setConfigurationCallback; - HermesGetConfigurationCallback m_getConfigurationCallback; - HermesDisconnectedCallback m_disconnectedCallback; - HermesTraceCallback m_traceCallback; -}; +**Lifecycle:** -// Inside m_getConfigurationCallback, call: -SignalHermesCurrentConfiguration(pSvc, sessionId, const HermesCurrentConfigurationData*); -// Inside m_setConfigurationCallback, call: -SignalHermesConfigurationNotification(pSvc, sessionId, const HermesNotificationData*); // on error only +```cpp +DownstreamSettings settings; +settings.m_machineId = "MyMachine"; +settings.m_port = 50100; -EnableHermesConfigurationService(pSvc, const HermesConfigurationServiceSettings*); -RunHermesConfigurationService(pSvc); // blocks -StopHermesConfigurationService(pSvc); -DeleteHermesConfigurationService(pSvc); +ds.Enable(settings); // starts listening, launches network thread +// ... application runs ... +ds.Stop(); // stops network thread, closes connection ``` -### 3.7 Vertical service API (machine side) +**Send messages to the Upstream:** + +```cpp +// Must call from inside Post() or from within a callback — both are on the network thread +ds.Post([&ds, sessionId]() { + BoardAvailableData board; + board.m_boardId = "BOARD-001"; + board.m_boardIdCreatedBy = "MyMachine"; + board.m_failedBoard = EBoardQuality::eGOOD; + board.m_flippedBoard = EFlippedBoard::eTOP_SIDE_IS_UP; + ds.Signal(sessionId, board); +}); +``` -The vertical channel connects a machine to a supervisory system (MES/ERP). +### Modern::Upstream -```c -HermesVerticalService* pVs = CreateHermesVerticalService( - const HermesVerticalServiceCallbacks* -); -EnableHermesVerticalService(pVs, const HermesVerticalServiceSettings*); -RunHermesVerticalService(pVs); - -// Signals: -SignalHermesVerticalServiceDescription(pVs, sessionId, const HermesSupervisoryServiceDescriptionData*); -SignalHermesQueryWorkOrderInfo(pVs, sessionId, const HermesQueryWorkOrderInfoData*); -SignalHermesReplyWorkOrderInfo(pVs, sessionId, const HermesReplyWorkOrderInfoData*); -SignalHermesVerticalServiceNotification(pVs, sessionId, const HermesNotificationData*); -SignalHermesVerticalServiceCheckAlive(pVs, sessionId, const HermesCheckAliveData*); -SignalHermesVerticalCurrentConfiguration(pVs, sessionId, const HermesCurrentConfigurationData*); -SignalHermesSendHermesCapabilities(pVs, sessionId, const HermesSendHermesCapabilitiesData*); - -// sessionId == 0 → broadcast to all clients with FeatureBoardTracking -SignalHermesBoardArrived(pVs, sessionId, const HermesBoardArrivedData*); -SignalHermesBoardDeparted(pVs, sessionId, const HermesBoardDepartedData*); - -ResetHermesVerticalServiceSession(pVs, sessionId, const HermesNotificationData*); -StopHermesVerticalService(pVs); -DeleteHermesVerticalService(pVs); +```cpp +Hermes::Modern::Upstream us(unsigned laneId); ``` -### 3.8 Vertical client API (supervisory system side) +**Register callbacks:** -```c -HermesVerticalClient* pVc = CreateHermesVerticalClient( - const HermesVerticalClientCallbacks* -); -EnableHermesVerticalClient(pVc, const HermesVerticalClientSettings*); -RunHermesVerticalClient(pVc); - -// Signals: -SignalHermesVerticalClientDescription(pVc, sessionId, const HermesSupervisoryServiceDescriptionData*); -SignalHermesSendWorkOrderInfo(pVc, sessionId, const HermesSendWorkOrderInfoData*); -SignalHermesVerticalGetConfiguration(pVc, sessionId, const HermesGetConfigurationData*); -SignalHermesVerticalSetConfiguration(pVc, sessionId, const HermesSetConfigurationData*); -SignalHermesVerticalQueryHermesCapabilities(pVc, sessionId, const HermesQueryHermesCapabilitiesData*); -SignalHermesVerticalClientNotification(pVc, sessionId, const HermesNotificationData*); -SignalHermesVerticalClientCheckAlive(pVc, sessionId, const HermesCheckAliveData*); -ResetHermesVerticalClient(pVc, const HermesNotificationData*); -StopHermesVerticalClient(pVc); -DeleteHermesVerticalClient(pVc); +```cpp +us.RegisterConnectedCallback( [](unsigned sessionId, const ConnectionInfo& info) { ... }); +us.RegisterDisconnectedCallback([](unsigned sessionId, const Error& err) { ... }); +us.RegisterStateChangeCallback( [](unsigned sessionId, EState state) { ... }); +us.RegisterTraceCallback( [](unsigned sessionId, ETraceType type, StringView msg) { ... }); + +// Messages received FROM the Downstream machine +us.RegisterServiceDescriptionCallback( [](unsigned sessionId, EState, const ServiceDescriptionData& d) { ... }); +us.RegisterBoardAvailableCallback( [](unsigned sessionId, EState, const BoardAvailableData& d) { ... }); +us.RegisterRevokeBoardAvailableCallback( [](unsigned sessionId, EState, const RevokeBoardAvailableData& d) { ... }); +us.RegisterTransportFinishedCallback( [](unsigned sessionId, EState, const TransportFinishedData& d) { ... }); +us.RegisterBoardForecastCallback( [](unsigned sessionId, EState, const BoardForecastData& d) { ... }); +us.RegisterSendBoardInfoCallback( [](unsigned sessionId, const SendBoardInfoData& d) { ... }); + +// Auxiliary +us.RegisterNotificationCallback([](unsigned sessionId, const NotificationData& d) { ... }); +us.RegisterCheckAliveCallback( [](unsigned sessionId, const CheckAliveData& d) { ... }); +us.RegisterCommandCallback( [](unsigned sessionId, const CommandData& d) { ... }); ``` ---- +**Lifecycle:** -## 4. C++ API +```cpp +UpstreamSettings settings; +settings.m_machineId = "MyMachine"; +settings.m_hostAddress = "192.168.1.2"; // IP of the Downstream machine +settings.m_port = 50100; -**Header:** `Hermes.hpp` (includes `HermesDataConversion.hpp` → `HermesData.hpp`) -Use this when you need full session control, `Post()`, raw XML, or are implementing a class that handles both Upstream and Downstream on the same object. +us.Enable(settings); +// ... +us.Stop(); +``` -### 4.1 Hermes::Downstream +**Send messages to the Downstream:** ```cpp -// Constructor — takes laneId and a reference to your callback implementation -Hermes::Downstream downstream(unsigned laneId, Hermes::IDownstreamCallback& callback); - -// Lifecycle -downstream.Enable(const DownstreamSettings&); // start listening -downstream.Run(); // blocks until Stop() -downstream.Stop(); // shut down -// Destructor calls DeleteHermesDownstream automatically - -// Post a callable onto the Hermes network thread (thread-safe from any thread) -downstream.Post([&]() { - downstream.Signal(sessionId, boardAvailableData); +us.Post([&us, sessionId]() { + MachineReadyData ready; + ready.m_failedBoard = EBoardQuality::eANY; + us.Signal(sessionId, ready); }); - -// Signals — call from within Post() or from a callback (both are on the network thread) -downstream.Signal(sessionId, const ServiceDescriptionData&); -downstream.Signal(sessionId, const BoardAvailableData&); -downstream.Signal(sessionId, const RevokeBoardAvailableData&); -downstream.Signal(sessionId, const TransportFinishedData&); -downstream.Signal(sessionId, const BoardForecastData&); -downstream.Signal(sessionId, const SendBoardInfoData&); -downstream.Signal(sessionId, const NotificationData&); -downstream.Signal(sessionId, const CheckAliveData&); -downstream.Signal(sessionId, const CommandData&); -downstream.Reset(const NotificationData&); -downstream.Disable(const NotificationData&); - -// Testing only -downstream.Signal(sessionId, StringView rawXml); -downstream.Reset(StringView rawXml); ``` -### 4.2 IDownstreamCallback — pure virtual interface +--- + +## 7. Low-level C++ API + +`Hermes.hpp` provides `Hermes::Downstream` and `Hermes::Upstream` with virtual callback interfaces. Use this when you need the session ID in every callback, raw XML signals, or multiple sessions. -Inherit from this and implement every pure virtual method. +### Implementing IDownstreamCallback ```cpp -struct MyDownstreamCallback : Hermes::IDownstreamCallback +struct MyDownstreamHandler : Hermes::IDownstreamCallback { - // Connection events — all pure virtual + // Pure virtuals — must implement all of these: void OnConnected(unsigned sessionId, EState, const ConnectionInfo&) override; void OnDisconnected(unsigned sessionId, EState, const Error&) override; void OnState(unsigned sessionId, EState) override; - void OnTrace(unsigned sessionId, ETraceType, StringView trace) override; + void OnTrace(unsigned sessionId, ETraceType, StringView) override; - // Messages received FROM the Upstream machine — pure virtual: + // Messages from Upstream — pure virtual: void On(unsigned sessionId, EState, const ServiceDescriptionData&) override; void On(unsigned sessionId, EState, const MachineReadyData&) override; void On(unsigned sessionId, EState, const RevokeMachineReadyData&) override; @@ -435,49 +452,36 @@ struct MyDownstreamCallback : Hermes::IDownstreamCallback void On(unsigned sessionId, const NotificationData&) override; void On(unsigned sessionId, const CommandData&) override; - // Optional — have default empty implementations (not pure virtual): + // Optional — have default empty implementations: void On(unsigned sessionId, const CheckAliveData&) override {} void On(unsigned sessionId, const QueryBoardInfoData&) override {} }; ``` -### 4.3 Hermes::Upstream +**Usage:** ```cpp -Hermes::Upstream upstream(unsigned laneId, Hermes::IUpstreamCallback& callback); - -upstream.Enable(const UpstreamSettings&); -upstream.Run(); -upstream.Stop(); - -upstream.Post([&]() { upstream.Signal(sessionId, machineReadyData); }); - -upstream.Signal(sessionId, const ServiceDescriptionData&); -upstream.Signal(sessionId, const MachineReadyData&); -upstream.Signal(sessionId, const RevokeMachineReadyData&); -upstream.Signal(sessionId, const StartTransportData&); -upstream.Signal(sessionId, const StopTransportData&); -upstream.Signal(sessionId, const QueryBoardInfoData&); -upstream.Signal(sessionId, const NotificationData&); -upstream.Signal(sessionId, const CheckAliveData&); -upstream.Signal(sessionId, const CommandData&); -upstream.Reset(const NotificationData&); -upstream.Disable(const NotificationData&); -upstream.Signal(sessionId, StringView rawXml); -upstream.Reset(StringView rawXml); +MyDownstreamHandler handler; +Hermes::Downstream downstream(1, handler); // lane 1 + +DownstreamSettings settings; +settings.m_machineId = "Machine-A"; +settings.m_port = 50100; +downstream.Enable(settings); +downstream.Run(); // blocks until Stop() is called from another thread ``` -### 4.4 IUpstreamCallback — pure virtual interface +### Implementing IUpstreamCallback ```cpp -struct MyUpstreamCallback : Hermes::IUpstreamCallback +struct MyUpstreamHandler : Hermes::IUpstreamCallback { void OnConnected(unsigned sessionId, EState, const ConnectionInfo&) override; void OnDisconnected(unsigned sessionId, EState, const Error&) override; void OnState(unsigned sessionId, EState) override; void OnTrace(unsigned sessionId, ETraceType, StringView) override; - // Messages received FROM the Downstream machine — pure virtual: + // Messages from Downstream — pure virtual: void On(unsigned sessionId, EState, const ServiceDescriptionData&) override; void On(unsigned sessionId, EState, const BoardAvailableData&) override; void On(unsigned sessionId, EState, const RevokeBoardAvailableData&) override; @@ -492,520 +496,300 @@ struct MyUpstreamCallback : Hermes::IUpstreamCallback }; ``` -### 4.5 Configuration service C++ wrapper +### Sending signals from within callbacks -```cpp -Hermes::ConfigurationService svc(Hermes::IConfigurationServiceCallback& callback); -svc.Enable(const ConfigurationServiceSettings&); -svc.Run(); -svc.Stop(); -svc.Post(callable); - -// Free functions for the client side: -auto [config, error] = Hermes::GetConfiguration("192.168.1.2", 10, nullptr); -Error err = Hermes::SetConfiguration("192.168.1.2", setConfigData, 10, nullptr, nullptr, nullptr); -``` - -### 4.6 Dual-role helper — UpstreamCallbackHelper / DownstreamCallbackHelper - -When one class implements both `IUpstreamCallback` and `IDownstreamCallback`, some `On()` method signatures clash. Use the helpers to disambiguate: +Always use `Post()` to send signals — it schedules the send on the Hermes network thread safely: ```cpp -struct MyHandler - : Hermes::UpstreamCallbackHelper - , Hermes::DownstreamCallbackHelper +void On(unsigned sessionId, EState, const MachineReadyData&) override { - // Upstream methods get "Upstream" prefix: - void OnUpstreamConnected(unsigned sessionId, EState, const ConnectionInfo&) override; - void OnUpstream(unsigned sessionId, EState, const ServiceDescriptionData&) override; - void OnUpstream(unsigned sessionId, const NotificationData&) override; - void OnUpstream(unsigned sessionId, const CheckAliveData&) override; - void OnUpstream(unsigned sessionId, const CommandData&) override; - void OnUpstreamState(unsigned sessionId, EState) override; - void OnUpstreamDisconnected(unsigned sessionId, EState, const Error&) override; - void OnUpstreamTrace(unsigned sessionId, ETraceType, StringView) override; - - // Downstream methods get "Downstream" prefix: - void OnDownstreamConnected(unsigned sessionId, EState, const ConnectionInfo&) override; - void OnDownstream(unsigned sessionId, EState, const ServiceDescriptionData&) override; - void OnDownstream(unsigned sessionId, const NotificationData&) override; - void OnDownstream(unsigned sessionId, const CheckAliveData&) override; - void OnDownstream(unsigned sessionId, const CommandData&) override; - void OnDownstreamState(unsigned sessionId, EState) override; - void OnDownstreamDisconnected(unsigned sessionId, EState, const Error&) override; - void OnDownstreamTrace(unsigned sessionId, ETraceType, StringView) override; - - // Unambiguous methods remain unchanged: - void On(unsigned sessionId, EState, const BoardAvailableData&) override; - void On(unsigned sessionId, EState, const MachineReadyData&) override; - // etc. -}; -``` - ---- - -## 5. Modern C++ API - -**Header:** `HermesModern.hpp` -**Namespace:** `Hermes::Modern` - -Uses `std::function` callbacks instead of virtual interfaces. Manages the network thread internally. Recommended for all new application code. - -### 5.1 Modern::Downstream - -```cpp -Hermes::Modern::Downstream ds(unsigned laneId); - -// Register callbacks before calling Enable() -ds.RegisterConnectedCallback( [](unsigned sessionId, const ConnectionInfo& info) { }); -ds.RegisterDisconnectedCallback([](unsigned sessionId, const Error& err) { }); -ds.RegisterStateChangeCallback( [](unsigned sessionId, EState state) { }); -ds.RegisterTraceCallback( [](unsigned sessionId, ETraceType, StringView msg) { }); - -// Messages received FROM the Upstream machine: -ds.RegisterServiceDescriptionCallback( [](unsigned sessionId, EState, const ServiceDescriptionData&) { }); -ds.RegisterMachineReadyCallback( [](unsigned sessionId, EState, const MachineReadyData&) { }); -ds.RegisterRevokeMachineReadyCallback( [](unsigned sessionId, EState, const RevokeMachineReadyData&) { }); -ds.RegisterStartTransportCallback( [](unsigned sessionId, EState, const StartTransportData&) { }); -ds.RegisterStopTransportCallback( [](unsigned sessionId, EState, const StopTransportData&) { }); -ds.RegisterQueryBoardInfoCallback( [](unsigned sessionId, const QueryBoardInfoData&) { }); - -// Auxiliary: -ds.RegisterNotificationCallback([](unsigned sessionId, const NotificationData&) { }); -ds.RegisterCheckAliveCallback( [](unsigned sessionId, const CheckAliveData&) { }); -ds.RegisterCommandCallback( [](unsigned sessionId, const CommandData&) { }); - -// Lifecycle -DownstreamSettings settings; -settings.m_machineId = "MyMachine"; -settings.m_port = 50100; -ds.Enable(settings); // starts listening, launches network thread -ds.Stop(); // blocks until network thread finishes. Safe to call multiple times. - -// Send messages TO the Upstream (call from inside Post() or from within a callback) -ds.Post([&ds, sessionId]() { - ds.Signal(sessionId, boardAvailableData); -}); + // DO NOT call Signal() directly here in the virtual callback API. + // Use Post() to dispatch onto the network thread: + m_downstream->Post([this, sessionId]() { + BoardAvailableData board; + board.m_boardId = "B-001"; + board.m_boardIdCreatedBy = "MachineA"; + board.m_failedBoard = EBoardQuality::eGOOD; + board.m_flippedBoard = EFlippedBoard::eTOP_SIDE_IS_UP; + m_downstream->Signal(sessionId, board); + }); +} ``` -### 5.2 Modern::Upstream - -```cpp -Hermes::Modern::Upstream us(unsigned laneId); - -us.RegisterConnectedCallback( [](unsigned sessionId, const ConnectionInfo&) { }); -us.RegisterDisconnectedCallback([](unsigned sessionId, const Error&) { }); -us.RegisterStateChangeCallback( [](unsigned sessionId, EState) { }); -us.RegisterTraceCallback( [](unsigned sessionId, ETraceType, StringView) { }); - -// Messages received FROM the Downstream machine: -us.RegisterServiceDescriptionCallback( [](unsigned sessionId, EState, const ServiceDescriptionData&) { }); -us.RegisterBoardAvailableCallback( [](unsigned sessionId, EState, const BoardAvailableData&) { }); -us.RegisterRevokeBoardAvailableCallback( [](unsigned sessionId, EState, const RevokeBoardAvailableData&) { }); -us.RegisterTransportFinishedCallback( [](unsigned sessionId, EState, const TransportFinishedData&) { }); -us.RegisterBoardForecastCallback( [](unsigned sessionId, EState, const BoardForecastData&) { }); -us.RegisterSendBoardInfoCallback( [](unsigned sessionId, const SendBoardInfoData&) { }); -us.RegisterNotificationCallback( [](unsigned sessionId, const NotificationData&) { }); -us.RegisterCheckAliveCallback( [](unsigned sessionId, const CheckAliveData&) { }); -us.RegisterCommandCallback( [](unsigned sessionId, const CommandData&) { }); - -UpstreamSettings settings; -settings.m_machineId = "MyMachine"; -settings.m_hostAddress = "192.168.1.2"; // downstream machine IP -settings.m_port = 50100; -us.Enable(settings); -us.Stop(); - -us.Post([&us, sessionId]() { - us.Signal(sessionId, machineReadyData); -}); -``` +> In `Modern::Downstream` / `Modern::Upstream`, callbacks already run on the network thread, so you can call `Signal()` directly inside them. --- -## 6. Serialization API +## 8. Serialization API -**C header:** `HermesSerialization.h` -**C++ header:** `HermesSerialization.hpp` - -### 6.1 C API - -```c -// Callback that receives the serialized XML string -struct HermesSerializationCallback { - void (*m_pCall)(void* m_pData, HermesStringView result); - void* m_pData; -}; - -// Serialize any message to XML: -HermesSerializeServiceDescription(const HermesServiceDescriptionData*, HermesSerializationCallback); -HermesSerializeBoardAvailable(const HermesBoardAvailableData*, HermesSerializationCallback); -HermesSerializeMachineReady(const HermesMachineReadyData*, HermesSerializationCallback); -HermesSerializeStartTransport(const HermesStartTransportData*, HermesSerializationCallback); -HermesSerializeStopTransport(const HermesStopTransportData*, HermesSerializationCallback); -HermesSerializeTransportFinished(const HermesTransportFinishedData*, HermesSerializationCallback); -HermesSerializeRevokeBoardAvailable(const HermesRevokeBoardAvailableData*, HermesSerializationCallback); -HermesSerializeRevokeMachineReady(const HermesRevokeMachineReadyData*, HermesSerializationCallback); -HermesSerializeBoardForecast(const HermesBoardForecastData*, HermesSerializationCallback); -HermesSerializeQueryBoardInfo(const HermesQueryBoardInfoData*, HermesSerializationCallback); -HermesSerializeSendBoardInfo(const HermesSendBoardInfoData*, HermesSerializationCallback); -HermesSerializeNotification(const HermesNotificationData*, HermesSerializationCallback); -HermesSerializeCheckAlive(const HermesCheckAliveData*, HermesSerializationCallback); -HermesSerializeGetConfiguration(const HermesGetConfigurationData*, HermesSerializationCallback); -HermesSerializeSetConfiguration(const HermesSetConfigurationData*, HermesSerializationCallback); -HermesSerializeCurrentConfiguration(const HermesCurrentConfigurationData*, HermesSerializationCallback); -HermesSerializeCommand(const HermesCommandData*, HermesSerializationCallback); -// Vertical: -HermesSerializeSupervisoryServiceDescription(...); -HermesSerializeBoardArrived(...); -HermesSerializeBoardDeparted(...); -HermesSerializeQueryWorkOrderInfo(...); -HermesSerializeSendWorkOrderInfo(...); -HermesSerializeReplyWorkOrderInfo(...); -HermesSerializeQueryHermesCapabilities(...); -HermesSerializeSendHermesCapabilities(...); - -// Deserialize — fills in whichever callback matches the XML message type -void HermesDeserialize(HermesStringView xml, const HermesDeserializationCallbacks*); -``` - -### 6.2 C++ API +`HermesSerialization.hpp` lets you convert any data struct to XML and back. Useful for logging, testing, or debugging. ```cpp #include "HermesSerialization.hpp" -// Serialize +// Serialize to XML string BoardAvailableData board; -board.m_boardId = "PCB-001"; -board.m_boardIdCreatedBy = "MachineA"; +board.m_boardId = "BOARD-001"; +board.m_boardIdCreatedBy = "Machine-A"; board.m_failedBoard = EBoardQuality::eGOOD; board.m_flippedBoard = EFlippedBoard::eTOP_SIDE_IS_UP; -board.m_optionalLengthInMM = 150.0; std::string xml = Hermes::ToXml(board); +// xml = "BOARD-001..." -// Deserialize +// Deserialize from XML string Hermes::Optional result = Hermes::FromXml(xml); if (result.has_value()) { - std::cout << result->m_boardId << "\n"; + std::cout << result->m_boardId << "\n"; // "BOARD-001" } ``` -`ToXml()` and `FromXml()` are available for all message types. +All message types are supported: ---- - -## 7. Data types reference +```cpp +Hermes::ToXml(const ServiceDescriptionData&) +Hermes::ToXml(const BoardAvailableData&) +Hermes::ToXml(const RevokeBoardAvailableData&) +Hermes::ToXml(const MachineReadyData&) +Hermes::ToXml(const RevokeMachineReadyData&) +Hermes::ToXml(const StartTransportData&) +Hermes::ToXml(const StopTransportData&) +Hermes::ToXml(const TransportFinishedData&) +Hermes::ToXml(const BoardForecastData&) +Hermes::ToXml(const QueryBoardInfoData&) +Hermes::ToXml(const SendBoardInfoData&) +Hermes::ToXml(const NotificationData&) +Hermes::ToXml(const CheckAliveData&) +Hermes::ToXml(const SetConfigurationData&) +Hermes::ToXml(const CurrentConfigurationData&) +Hermes::ToXml(const GetConfigurationData&) +Hermes::ToXml(const CommandData&) -All types are in `HermesData.hpp` (C++) or `HermesData.h` (C). +// Vertical: +Hermes::ToXml(const SupervisoryServiceDescriptionData&) +Hermes::ToXml(const BoardArrivedData&) +Hermes::ToXml(const BoardDepartedData&) +Hermes::ToXml(const QueryWorkOrderInfoData&) +Hermes::ToXml(const SendWorkOrderInfoData&) +Hermes::ToXml(const ReplyWorkOrderInfoData&) +Hermes::ToXml(const QueryHermesCapabilitiesData&) +Hermes::ToXml(const SendHermesCapabilitiesData&) +``` -### Key enums +--- -| C++ enum | Values | -|----------|--------| -| `EState` | `eNOT_CONNECTED`, `eSOCKET_CONNECTED`, `eSERVICE_DESCRIPTION_DOWNSTREAM`, `eNOT_AVAILABLE_NOT_READY`, `eBOARD_AVAILABLE`, `eMACHINE_READY`, `eAVAILABLE_AND_READY`, `eTRANSPORTING`, `eTRANSPORT_STOPPED`, `eTRANSPORT_FINISHED`, `eDISCONNECTED` | -| `ETraceType` | `eSENT`, `eRECEIVED`, `eDEBUG`, `eINFO`, `eWARNING`, `eERROR` | -| `EBoardQuality` | `eANY`, `eGOOD`, `eBAD` | -| `EFlippedBoard` | `eSIDE_UP_IS_UNKNOWN`, `eTOP_SIDE_IS_UP`, `eBOTTOM_SIDE_IS_UP` | -| `ETransferState` | `eUNKNOWN`, `eNOT_STARTED`, `eINCOMPLETE`, `eCOMPLETE` | -| `ENotificationCode` | `eUNSPECIFIC`, `ePROTOCOL_ERROR`, `eCONNECTION_REFUSED_BECAUSE_OF_ESTABLISHED_CONNECTION`, `eCONNECTION_RESET_BECAUSE_OF_CHANGED_CONFIGURATION`, `eCONFIGURATION_ERROR`, `eMACHINE_SHUTDOWN`, `eBOARD_FORECAST_ERROR` | -| `ESeverity` | `eUNKNOWN`, `eFATAL`, `eERROR`, `eWARNING`, `eINFO` | -| `EErrorCode` | `eSUCCESS`, `eIMPLEMENTATION_ERROR`, `ePEER_ERROR`, `eCLIENT_ERROR`, `eNETWORK_ERROR`, `eTIMEOUT` | -| `EVerticalState` | `eNOT_CONNECTED`, `eSOCKET_CONNECTED`, `eSUPERVISORY_SERVICE_DESCRIPTION`, `eCONNECTED`, `eDISCONNECTED` | +## 9. Configuration service -### Key C++ structs (HermesData.hpp) +The configuration service allows a remote tool (e.g. a factory MES) to read and write the machine's lane configuration over TCP on port 1248. ```cpp -// ServiceDescriptionData — exchanged by both sides at connection start -struct ServiceDescriptionData { - std::string m_machineId; // required - uint32_t m_laneId; // required, 1-based - Optional m_optionalInterfaceId; - std::string m_version; - // SupportedFeatures omitted for brevity -}; - -// BoardAvailableData — sent by Downstream to signal a board is ready -struct BoardAvailableData { - std::string m_boardId; // required - std::string m_boardIdCreatedBy; // required - EBoardQuality m_failedBoard; // required - EFlippedBoard m_flippedBoard; // required - Optional m_optionalProductTypeId; - Optional m_optionalTopBarcode; - Optional m_optionalBottomBarcode; - Optional m_optionalLengthInMM; - Optional m_optionalWidthInMM; - Optional m_optionalThicknessInMM; - Optional m_optionalConveyorSpeedInMMPerSecs; - Optional m_optionalTopClearanceHeightInMM; - Optional m_optionalBottomClearanceHeightInMM; - Optional m_optionalWeightInGrams; - Optional m_optionalWorkOrderId; - Optional m_optionalBatchId; - Optional m_optionalRoute; - Optional m_optionalAction; - SubBoards m_optionalSubBoards; // std::vector -}; +#include "Hermes.hpp" -// MachineReadyData — sent by Upstream to signal it can receive a board -struct MachineReadyData { - EBoardQuality m_failedBoard; // required — what quality it accepts - Optional m_optionalForecastId; - Optional m_optionalBoardId; - Optional m_optionalProductTypeId; - Optional m_optionalFlippedBoard; - Optional m_optionalTopBarcode; - Optional m_optionalBottomBarcode; - Optional m_optionalLengthInMM; - Optional m_optionalWidthInMM; - Optional m_optionalThicknessInMM; - Optional m_optionalConveyorSpeedInMMPerSecs; - Optional m_optionalTopClearanceHeightInMM; - Optional m_optionalBottomClearanceHeightInMM; - Optional m_optionalWeightInGrams; - Optional m_optionalWorkOrderId; - Optional m_optionalBatchId; -}; +struct MyConfigHandler : Hermes::IConfigurationServiceCallback +{ + void OnConnected(unsigned sessionId, const ConnectionInfo& info) override + { + std::cout << "Config client connected: " << info.m_address << "\n"; + } -// StartTransportData — Upstream tells Downstream to release the board -struct StartTransportData { - std::string m_boardId; // required - Optional m_optionalConveyorSpeedInMMPerSecs; // optional override -}; + // Remote client reads our configuration + CurrentConfigurationData OnGetConfiguration(unsigned, const ConnectionInfo&) override + { + CurrentConfigurationData config; + config.m_optionalMachineId = "Machine-A"; -// StopTransportData — Upstream confirms board received -struct StopTransportData { - ETransferState m_transferState; // eCOMPLETE, eINCOMPLETE, eNOT_STARTED - std::string m_boardId; -}; + UpstreamConfiguration up; + up.m_upstreamLaneId = 1; + up.m_hostAddress = "192.168.1.2"; + up.m_port = 50100; + config.m_upstreamConfigurations.push_back(up); -// TransportFinishedData — Downstream confirms board released -struct TransportFinishedData { - ETransferState m_transferState; - std::string m_boardId; -}; + return config; + } -// NotificationData — error or status from either side -struct NotificationData { - ENotificationCode m_notificationCode; - ESeverity m_severity; - std::string m_description; -}; + // Remote client writes a new configuration — return Error{} for success + Error OnSetConfiguration(unsigned, const ConnectionInfo&, + const SetConfigurationData& newConfig) override + { + // Apply newConfig to your machine... + return Error{}; // success + } -// Error — returned in disconnect callbacks and configuration calls -struct Error { - EErrorCode m_code; // eSUCCESS = no error - std::string m_text; - explicit operator bool() const; // true if error + void OnDisconnected(unsigned, const Error&) override {} + void OnTrace(unsigned, ETraceType, StringView) override {} }; -// ConnectionInfo -struct ConnectionInfo { - std::string m_address; - uint16_t m_port; - std::string m_hostName; -}; +// Usage: +MyConfigHandler handler; +Hermes::ConfigurationService service(handler); -// Settings -struct DownstreamSettings { - std::string m_machineId; - Optional m_optionalClientAddress; // restrict to one IP - uint16_t m_port{0}; // 0 = cBASE_PORT (50100) - double m_checkAlivePeriodInSeconds{60}; - double m_reconnectWaitTimeInSeconds{10}; - ECheckAliveResponseMode m_checkAliveResponseMode{ECheckAliveResponseMode::eAUTO}; - ECheckState m_checkState{ECheckState::eSEND_AND_RECEIVE}; -}; +ConfigurationServiceSettings settings; +settings.m_port = cCONFIG_PORT; // 1248 -struct UpstreamSettings { - std::string m_machineId; - std::string m_hostAddress; // downstream machine IP — required - uint16_t m_port{0}; - double m_checkAlivePeriodInSeconds{60}; - double m_reconnectWaitTimeInSeconds{10}; - ECheckAliveResponseMode m_checkAliveResponseMode{ECheckAliveResponseMode::eAUTO}; - ECheckState m_checkState{ECheckState::eSEND_AND_RECEIVE}; -}; +service.Enable(settings); +service.Run(); // blocks ``` ---- +### Remote configuration client -## 8. Error handling +Query or set a machine's configuration from another process: ```cpp -void OnDisconnected(unsigned sessionId, EState, const Error& error) override -{ - if (error) { // operator bool() — true if not eSUCCESS - std::cerr << "Error " << error.m_code << ": " << error.m_text << "\n"; - // EErrorCode values: - // eSUCCESS — not an error - // eIMPLEMENTATION_ERROR — bug inside the Hermes library - // ePEER_ERROR — remote machine misbehaved - // eCLIENT_ERROR — your code called the API incorrectly - // eNETWORK_ERROR — TCP/IP problem - // eTIMEOUT — timeout exceeded - } +// Get configuration +auto [config, error] = Hermes::GetConfiguration("192.168.1.2", 10, nullptr); +if (!error) { + std::cout << "Machine: " << config.m_optionalMachineId.value_or("unknown") << "\n"; } -``` - -Notification codes (`ENotificationCode`) are what the **protocol** sends to describe issues: -```cpp -void On(unsigned sessionId, const NotificationData& n) override -{ - // n.m_notificationCode: - // eUNSPECIFIC - // ePROTOCOL_ERROR - // eCONNECTION_REFUSED_BECAUSE_OF_ESTABLISHED_CONNECTION - // eCONNECTION_RESET_BECAUSE_OF_CHANGED_CONFIGURATION - // eCONFIGURATION_ERROR - // eMACHINE_SHUTDOWN - // eBOARD_FORECAST_ERROR - - // n.m_severity: eFATAL, eERROR, eWARNING, eINFO - // n.m_description: human-readable string -} +// Set configuration +SetConfigurationData newConfig; +newConfig.m_machineId = "Machine-A"; + +Error err = Hermes::SetConfiguration( + "192.168.1.2", // host + newConfig, // configuration to write + 10, // timeout seconds + nullptr, // optional: resulting config out + nullptr, // optional: notifications out + nullptr // optional: trace callback +); ``` --- -## 9. Thread safety rules - -The Hermes library runs its own event loop thread internally. +## 10. Vertical interface -**Safe to call from any thread:** -- `Enable()`, `Stop()` -- `Post(callable)` — schedules work onto the Hermes thread - -**Must only be called from the Hermes thread** (i.e. from within a callback or from a `Post()` lambda): -- All `Signal()` methods -- `Reset()`, `Disable()` - -**Trace callbacks are called from the Hermes thread** — your trace handler must be thread-safe if it writes shared state. - -**Pattern for correct signal dispatch:** +The vertical interface connects a machine to a supervisory system (MES/ERP) on a separate TCP port. `VerticalService` is the machine side. `VerticalClient` is the supervisory system side. ```cpp -// Inside a callback (already on Hermes thread) — direct call is fine -void On(unsigned sessionId, EState, const MachineReadyData&) override +struct MyVerticalHandler : Hermes::IVerticalServiceCallback { - BoardAvailableData board; - board.m_boardId = "PCB-001"; - board.m_boardIdCreatedBy = "MyMachine"; - board.m_failedBoard = EBoardQuality::eGOOD; - board.m_flippedBoard = EFlippedBoard::eTOP_SIDE_IS_UP; - m_downstream.Signal(sessionId, board); // safe — we're on the Hermes thread -} + void OnConnected(unsigned sessionId, EVerticalState, const ConnectionInfo&) override {} -// From your own thread — use Post() -void SendFromMyThread(unsigned sessionId) -{ - m_downstream.Post([this, sessionId]() { - BoardAvailableData board; - // ... fill board ... - m_downstream.Signal(sessionId, board); - }); -} + void On(unsigned sessionId, EVerticalState, + const SupervisoryServiceDescriptionData& data) override + { + std::cout << "Supervisor connected: " << data.m_systemId << "\n"; + } + + void On(unsigned sessionId, const GetConfigurationData&, const ConnectionInfo&) override {} + void On(unsigned sessionId, const SetConfigurationData&, const ConnectionInfo&) override {} + void On(unsigned sessionId, const NotificationData&) override {} + void On(unsigned sessionId, const QueryHermesCapabilitiesData&) override {} + void OnDisconnected(unsigned sessionId, EVerticalState, const Error&) override {} + void OnTrace(unsigned sessionId, ETraceType, StringView) override {} +}; ``` --- -## 10. Complete examples +## 11. Complete examples -### Example A — Downstream machine (C++) +### Example 1 — Downstream machine (listens, receives a board) ```cpp -#include "Hermes.hpp" +#include "HermesModern.hpp" #include #include +#include #include #include static std::atomic g_running{true}; -struct MyDownstream : Hermes::IDownstreamCallback +int main() { - Hermes::Downstream* m_ds = nullptr; + std::signal(SIGINT, [](int) { g_running = false; }); - void OnConnected(unsigned sessionId, Hermes::EState, const Hermes::ConnectionInfo& info) override { - std::cout << "[DS] Connected: " << info.m_address << "\n"; - } - void OnDisconnected(unsigned sessionId, Hermes::EState, const Hermes::Error& err) override { - std::cout << "[DS] Disconnected\n"; - } - void OnState(unsigned, Hermes::EState state) override { + Hermes::Modern::Downstream ds(1); // lane 1 + + // --- Connection events --- + ds.RegisterConnectedCallback([](unsigned sessionId, const Hermes::ConnectionInfo& info) { + std::cout << "[DS] Upstream connected from " << info.m_address << "\n"; + }); + + ds.RegisterDisconnectedCallback([](unsigned sessionId, const Hermes::Error& err) { + std::cout << "[DS] Upstream disconnected\n"; + }); + + ds.RegisterStateChangeCallback([](unsigned sessionId, Hermes::EState state) { std::cout << "[DS] State: " << state << "\n"; - } - void OnTrace(unsigned, Hermes::ETraceType, Hermes::StringView) override {} - - void On(unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) override { - std::cout << "[DS] ServiceDescription from: " << data.m_machineId << "\n"; - m_ds->Post([this, sessionId]() { - Hermes::ServiceDescriptionData reply; - reply.m_machineId = "DownstreamMachine"; - reply.m_laneId = 1; - m_ds->Signal(sessionId, reply); + }); + + // --- Receive ServiceDescription from Upstream, reply with ours --- + ds.RegisterServiceDescriptionCallback( + [&ds](unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) { + std::cout << "[DS] Received ServiceDescription from: " << data.m_machineId << "\n"; + + // Reply with our own ServiceDescription + ds.Post([&ds, sessionId]() { + Hermes::ServiceDescriptionData reply; + reply.m_machineId = "Machine-Downstream"; + reply.m_laneId = 1; + ds.Signal(sessionId, reply); + std::cout << "[DS] Sent ServiceDescription\n"; + }); }); - } - void On(unsigned sessionId, Hermes::EState, const Hermes::MachineReadyData&) override { - std::cout << "[DS] MachineReady — sending BoardAvailable\n"; - m_ds->Post([this, sessionId]() { - Hermes::BoardAvailableData board; - board.m_boardId = "PCB-001"; - board.m_boardIdCreatedBy = "DownstreamMachine"; - board.m_failedBoard = Hermes::EBoardQuality::eGOOD; - board.m_flippedBoard = Hermes::EFlippedBoard::eTOP_SIDE_IS_UP; - m_ds->Signal(sessionId, board); + // --- When Upstream is MachineReady, send BoardAvailable --- + ds.RegisterMachineReadyCallback( + [&ds](unsigned sessionId, Hermes::EState, const Hermes::MachineReadyData&) { + std::cout << "[DS] Upstream is MachineReady — sending BoardAvailable\n"; + + ds.Post([&ds, sessionId]() { + Hermes::BoardAvailableData board; + board.m_boardId = "PCB-2024-001"; + board.m_boardIdCreatedBy = "Machine-Downstream"; + board.m_failedBoard = Hermes::EBoardQuality::eGOOD; + board.m_flippedBoard = Hermes::EFlippedBoard::eTOP_SIDE_IS_UP; + ds.Signal(sessionId, board); + }); }); - } - void On(unsigned sessionId, Hermes::EState, const Hermes::StartTransportData& data) override { - std::cout << "[DS] StartTransport: " << data.m_boardId << "\n"; - m_ds->Post([this, sessionId, id = data.m_boardId]() { - Hermes::TransportFinishedData fin; - fin.m_transferState = Hermes::ETransferState::eCOMPLETE; - fin.m_boardId = id; - m_ds->Signal(sessionId, fin); + // --- When Upstream starts transport, confirm when done --- + ds.RegisterStartTransportCallback( + [&ds](unsigned sessionId, Hermes::EState, const Hermes::StartTransportData& data) { + std::cout << "[DS] StartTransport for board: " << data.m_boardId << "\n"; + + // Simulate board moving + std::this_thread::sleep_for(std::chrono::seconds(1)); + + ds.Post([&ds, sessionId, boardId = data.m_boardId]() { + Hermes::TransportFinishedData finished; + finished.m_transferState = Hermes::ETransferState::eCOMPLETE; + finished.m_boardId = boardId; + ds.Signal(sessionId, finished); + std::cout << "[DS] TransportFinished sent\n"; + }); }); - } - void On(unsigned, Hermes::EState, const Hermes::RevokeMachineReadyData&) override {} - void On(unsigned, Hermes::EState, const Hermes::StopTransportData&) override {} - void On(unsigned, const Hermes::NotificationData& n) override { + // --- Notifications --- + ds.RegisterNotificationCallback([](unsigned, const Hermes::NotificationData& n) { std::cout << "[DS] Notification: " << n.m_description << "\n"; - } - void On(unsigned, const Hermes::CommandData&) override {} -}; - -int main() -{ - std::signal(SIGINT, [](int) { g_running = false; }); + }); - MyDownstream cb; - Hermes::Downstream ds(1, cb); - cb.m_ds = &ds; + // --- Start --- + Hermes::DownstreamSettings settings; + settings.m_machineId = "Machine-Downstream"; + settings.m_port = 50100; + ds.Enable(settings); - Hermes::DownstreamSettings s; - s.m_machineId = "DownstreamMachine"; - s.m_port = 50100; - ds.Enable(s); + std::cout << "[DS] Listening on port 50100. Ctrl+C to stop.\n"; + while (g_running) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); - std::thread t([&ds]() { ds.Run(); }); - std::cout << "[DS] Listening on :50100\n"; - while (g_running) std::this_thread::sleep_for(std::chrono::milliseconds(100)); ds.Stop(); - t.join(); + return 0; } ``` -### Example B — Upstream machine (Modern C++) +### Example 2 — Upstream machine (connects, sends MachineReady) ```cpp #include "HermesModern.hpp" #include #include +#include #include #include @@ -1015,21 +799,33 @@ int main() { std::signal(SIGINT, [](int) { g_running = false; }); - Hermes::Modern::Upstream us(1); + Hermes::Modern::Upstream us(1); // lane 1 + + us.RegisterConnectedCallback([](unsigned sessionId, const Hermes::ConnectionInfo& info) { + std::cout << "[US] Connected to downstream at " << info.m_address << "\n"; + }); - us.RegisterConnectedCallback([](unsigned, const Hermes::ConnectionInfo& info) { - std::cout << "[US] Connected to " << info.m_address << "\n"; + us.RegisterDisconnectedCallback([](unsigned sessionId, const Hermes::Error& err) { + std::cout << "[US] Disconnected\n"; }); + us.RegisterStateChangeCallback([](unsigned sessionId, Hermes::EState state) { + std::cout << "[US] State: " << state << "\n"; + }); + + // --- Receive ServiceDescription, reply and send MachineReady --- us.RegisterServiceDescriptionCallback( [&us](unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) { - std::cout << "[US] ServiceDescription from: " << data.m_machineId << "\n"; + std::cout << "[US] Received ServiceDescription from: " << data.m_machineId << "\n"; + us.Post([&us, sessionId]() { + // Send our ServiceDescription Hermes::ServiceDescriptionData reply; - reply.m_machineId = "UpstreamMachine"; + reply.m_machineId = "Machine-Upstream"; reply.m_laneId = 1; us.Signal(sessionId, reply); + // Then send MachineReady Hermes::MachineReadyData ready; ready.m_failedBoard = Hermes::EBoardQuality::eANY; us.Signal(sessionId, ready); @@ -1037,24 +833,30 @@ int main() }); }); + // --- Receive BoardAvailable, trigger StartTransport --- us.RegisterBoardAvailableCallback( [&us](unsigned sessionId, Hermes::EState, const Hermes::BoardAvailableData& board) { std::cout << "[US] BoardAvailable: " << board.m_boardId << "\n"; - us.Post([&us, sessionId, id = board.m_boardId]() { + + us.Post([&us, sessionId, boardId = board.m_boardId]() { Hermes::StartTransportData start; - start.m_boardId = id; + start.m_boardId = boardId; us.Signal(sessionId, start); + std::cout << "[US] StartTransport sent\n"; }); }); + // --- Receive TransportFinished, send StopTransport --- us.RegisterTransportFinishedCallback( [&us](unsigned sessionId, Hermes::EState, const Hermes::TransportFinishedData& data) { - std::cout << "[US] TransportFinished: " << data.m_boardId << " — transfer complete\n"; - us.Post([&us, sessionId, id = data.m_boardId]() { + std::cout << "[US] TransportFinished for board: " << data.m_boardId << "\n"; + + us.Post([&us, sessionId, boardId = data.m_boardId]() { Hermes::StopTransportData stop; stop.m_transferState = Hermes::ETransferState::eCOMPLETE; - stop.m_boardId = id; + stop.m_boardId = boardId; us.Signal(sessionId, stop); + std::cout << "[US] StopTransport sent — full board transfer complete\n"; }); }); @@ -1062,42 +864,49 @@ int main() std::cout << "[US] Notification: " << n.m_description << "\n"; }); - Hermes::UpstreamSettings s; - s.m_machineId = "UpstreamMachine"; - s.m_hostAddress = "192.168.1.2"; // ← downstream machine IP - s.m_port = 50100; - us.Enable(s); + // EDIT THIS — set the downstream machine's IP: + Hermes::UpstreamSettings settings; + settings.m_machineId = "Machine-Upstream"; + settings.m_hostAddress = "192.168.1.2"; // <-- downstream machine IP + settings.m_port = 50100; + us.Enable(settings); + + std::cout << "[US] Connecting to 192.168.1.2:50100. Ctrl+C to stop.\n"; + while (g_running) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); - std::cout << "[US] Connecting to 192.168.1.2:50100\n"; - while (g_running) std::this_thread::sleep_for(std::chrono::milliseconds(100)); us.Stop(); + return 0; } ``` ---- - -## 11. Bugs fixed - -### HermesStringView.h -- Missing `#include ` — caused `size_t` to be undefined, breaking every dependent file. -- `m_size` field was absent from the struct — caused `HermesDataConversion.hpp` to fail on every string conversion. - -### HermesOptional.hpp -- Custom `Optional` replaced with `using Optional = std::optional`. C++17 is required, so `std::optional` is correct. -- Added `operator<<` for `std::optional` in `namespace Hermes` — required by internal `BuildString()` usage in `AsioServer.cpp` and other `.cpp` files that log `Optional` values. +### Example 3 — XML serialization and inspection -### HermesStringView.hpp -- Custom `StringView` replaced with `using StringView = std::string_view`. C++17 is required. - -### HermesDataConversion.hpp -- **`Converter2C` typo** — corrected to `Converter2C`. Was a hard link error. -- **`uint32_t` / `unsigned` redefinition** — on ARM (Raspberry Pi), `unsigned` and `uint32_t` are the same type. The separate `uint32_t` overload caused a redefinition error. Removed the duplicate overload — the `unsigned` overload covers both. -- **Wrong field names in `BoardAvailableData` converter** — used invented short names (`m_length`, `m_width`, `m_thickness`, `m_conveyorSpeed`, `m_topClearanceHeight`, `m_bottomClearanceHeight`, `m_weight`, `m_subBoards`). Corrected to actual field names: `m_optionalLengthInMM`, `m_optionalWidthInMM`, `m_optionalThicknessInMM`, `m_optionalConveyorSpeedInMMPerSecs`, `m_optionalTopClearanceHeightInMM`, `m_optionalBottomClearanceHeightInMM`, `m_optionalWeightInGrams`, `m_optionalSubBoards`. -- **Same wrong field names in `MachineReadyData` converter** — same fix applied. -- **Wrong field name in `StartTransportData` converter** — `m_conveyorSpeed` → `m_optionalConveyorSpeedInMMPerSecs`. +```cpp +#include "HermesSerialization.hpp" +#include -### HermesModern.hpp -- `Modern::Downstream::InternalCallbackWrapper` overrode the wrong message set (board messages instead of machine-control messages). Fixed to match `IDownstreamCallback` exactly. -- `Modern::Upstream::InternalCallbackWrapper` had the same problem in reverse. Fixed to match `IUpstreamCallback` exactly. -- `Stop()` was not safe to call twice — fixed using `std::atomic::exchange`. -- Callback signatures were missing `sessionId` — restored to full signature. +int main() +{ + // Serialize + Hermes::BoardAvailableData board; + board.m_boardId = "PCB-001"; + board.m_boardIdCreatedBy = "Machine-A"; + board.m_failedBoard = Hermes::EBoardQuality::eGOOD; + board.m_flippedBoard = Hermes::EFlippedBoard::eTOP_SIDE_IS_UP; + board.m_length = 150.0; + board.m_width = 100.0; + + std::string xml = Hermes::ToXml(board); + std::cout << "XML:\n" << xml << "\n"; + + // Deserialize + auto result = Hermes::FromXml(xml); + if (result.has_value()) { + std::cout << "BoardId: " << result->m_boardId << "\n"; + if (result->m_length.has_value()) + std::cout << "Length: " << *result->m_length << "mm\n"; + } + return 0; +} +``` \ No newline at end of file diff --git a/src/include/HermesDataConversion.hpp b/src/include/HermesDataConversion.hpp index ca1453d..6f20745 100644 --- a/src/include/HermesDataConversion.hpp +++ b/src/include/HermesDataConversion.hpp @@ -819,4 +819,4 @@ namespace Hermes return result; } -} // namespace Hermes +} // namespace Hermes \ No newline at end of file diff --git a/src/include/HermesOptional.hpp b/src/include/HermesOptional.hpp index dffa37d..b7a5c1a 100644 --- a/src/include/HermesOptional.hpp +++ b/src/include/HermesOptional.hpp @@ -49,4 +49,4 @@ namespace Hermes s << ""; return s; } -} +} \ No newline at end of file From d2f6ca5f1b282f039cdf77a8766b973080a76ad8 Mon Sep 17 00:00:00 2001 From: sahil agarwal Date: Fri, 27 Mar 2026 14:28:36 +0530 Subject: [PATCH 11/14] Revert "fix" This reverts commit db9acc17becc8a56f7dca655bfed01a7a82c29ee. --- src/include/HermesDataConversion.hpp | 406 +++++++++++++-------------- src/include/HermesOptional.hpp | 31 +- 2 files changed, 202 insertions(+), 235 deletions(-) diff --git a/src/include/HermesDataConversion.hpp b/src/include/HermesDataConversion.hpp index 6f20745..505a9f1 100644 --- a/src/include/HermesDataConversion.hpp +++ b/src/include/HermesDataConversion.hpp @@ -23,26 +23,31 @@ limitations under the License. namespace Hermes { // Verify constants match between C and C++ headers - static_assert(cCONFIG_PORT == cHERMES_CONFIG_PORT, "config port mismatch"); - static_assert(cBASE_PORT == cHERMES_BASE_PORT, "base port mismatch"); - static_assert(cMAX_MESSAGE_SIZE == cHERMES_MAX_MESSAGE_SIZE, "max message size mismatch"); + static_assert(cCONFIG_PORT == cHERMES_CONFIG_PORT, ""); + static_assert(cBASE_PORT == cHERMES_BASE_PORT, ""); + static_assert(cMAX_MESSAGE_SIZE == cHERMES_MAX_MESSAGE_SIZE, ""); // ------------------------------------------------------------------------- // String conversions + // FIX: HermesStringView now has m_pData and m_size (fixed in HermesStringView.h) + // FIX: std::optional uses value_or and has_value instead of custom Optional // ------------------------------------------------------------------------- inline void CppToC(const std::string& data, HermesStringView& result) { result.m_pData = data.data(); result.m_size = data.size(); } + inline void CToCpp(HermesStringView data, std::string& result) { result.assign(data.m_pData, data.m_size); } + inline HermesStringView ToC(StringView data) { return { data.data(), data.size() }; } + inline StringView ToCpp(HermesStringView data) { return { data.m_pData, data.m_size }; @@ -50,26 +55,37 @@ namespace Hermes inline void CppToC(const Optional& data, HermesStringView& result) { - if (data.has_value()) { result.m_pData = data->data(); result.m_size = data->size(); } - else { result.m_pData = nullptr; result.m_size = 0; } + if (data.has_value()) + { + result.m_pData = data->data(); + result.m_size = data->size(); + } + else + { + result.m_pData = nullptr; + result.m_size = 0; + } } + inline void CToCpp(HermesStringView data, Optional& result) { - result = data.m_pData ? Optional(std::in_place, data.m_pData, data.m_size) - : std::nullopt; + if (data.m_pData) + result = std::string(data.m_pData, data.m_size); + else + result = std::nullopt; } // ------------------------------------------------------------------------- // Scalar pass-throughs - // FIX: removed separate uint32_t overload — on ARM/RPi, unsigned == uint32_t, - // causing a redefinition error. One overload covers both. // ------------------------------------------------------------------------- - inline void CppToC(double data, double& result) { result = data; } - inline void CToCpp(double data, double& result) { result = data; } + inline void CppToC(double data, double& result) { result = data; } + inline void CToCpp(double data, double& result) { result = data; } + inline void CppToC(unsigned data, unsigned& result) { result = data; } + inline void CToCpp(unsigned data, unsigned& result) { result = data; } inline void CppToC(uint16_t data, uint16_t& result) { result = data; } inline void CToCpp(uint16_t data, uint16_t& result) { result = data; } - inline void CppToC(unsigned data, unsigned& result) { result = data; } - inline void CToCpp(unsigned data, unsigned& result) { result = data; } + inline void CppToC(uint32_t data, uint32_t& result) { result = data; } + inline void CToCpp(uint32_t data, uint32_t& result) { result = data; } // ------------------------------------------------------------------------- // Optional pointer conversions @@ -77,7 +93,8 @@ namespace Hermes template void CToCpp(const CT* pData, CppT& result) { - if (pData) CToCpp(*pData, result); + if (!pData) return; + CToCpp(*pData, result); } template @@ -90,9 +107,18 @@ namespace Hermes void CToCpp(const CT* pData, Optional& result) { if (!pData) { result = std::nullopt; return; } - CppT v{}; - CToCpp(*pData, v); - result = std::move(v); + CppT value{}; + CToCpp(*pData, value); + result = std::move(value); + } + + template + void CppToC(const Optional& data, Optional& result) + { + if (!data.has_value()) { result = std::nullopt; return; } + CT c{}; + CppToC(*data, c); + result = std::move(c); } template @@ -113,6 +139,15 @@ namespace Hermes std::vector m_pointers; }; + template + void CppToC(const std::vector& data, VectorHolder& result) + { + auto sz = data.size(); + result.m_values.resize(sz, CT{}); + for (uint32_t i = 0; i < sz; ++i) + CppToC(data[i], result.m_values[i]); + } + template void CppToC(const std::vector& data, VectorHolder& intermediate, CVector& result) { @@ -124,7 +159,7 @@ namespace Hermes CppToC(data[i], intermediate.m_values[i]); intermediate.m_pointers[i] = &intermediate.m_values[i]; } - result.m_pData = sz == 0 ? nullptr : intermediate.m_pointers.data(); + result.m_pData = (sz == 0) ? nullptr : intermediate.m_pointers.data(); result.m_size = sz; } @@ -137,90 +172,89 @@ namespace Hermes } // ------------------------------------------------------------------------- - // Enum conversions + // Enum conversions — static_assert verifies C and C++ enums stay in sync // ------------------------------------------------------------------------- static_assert(size(EState()) == cHERMES_STATE_ENUM_SIZE, "enum mismatch"); - inline EHermesState ToC(EState d) { return static_cast(d); } - inline EState ToCpp(EHermesState d) { return static_cast(d); } + inline EHermesState ToC(EState data) { return static_cast(data); } + inline EState ToCpp(EHermesState data) { return static_cast(data); } static_assert(size(ETraceType()) == cHERMES_TRACE_TYPE_ENUM_SIZE, "enum mismatch"); - inline EHermesTraceType ToC(ETraceType d) { return static_cast(d); } - inline ETraceType ToCpp(EHermesTraceType d) { return static_cast(d); } + inline EHermesTraceType ToC(ETraceType data) { return static_cast(data); } + inline ETraceType ToCpp(EHermesTraceType data) { return static_cast(data); } static_assert(size(ECheckAliveType()) == cHERMES_CHECK_ALIVE_TYPE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckAliveType d, EHermesCheckAliveType& r) { r = static_cast(d); } - inline void CToCpp(EHermesCheckAliveType d, ECheckAliveType& r) { r = static_cast(d); } + inline void CppToC(ECheckAliveType data, EHermesCheckAliveType& r) { r = static_cast(data); } + inline void CToCpp(EHermesCheckAliveType data, ECheckAliveType& r) { r = static_cast(data); } static_assert(size(EBoardQuality()) == cHERMES_BOARD_QUALITY_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardQuality d, EHermesBoardQuality& r) { r = static_cast(d); } - inline void CToCpp(EHermesBoardQuality d, EBoardQuality& r) { r = static_cast(d); } + inline void CppToC(EBoardQuality data, EHermesBoardQuality& r) { r = static_cast(data); } + inline void CToCpp(EHermesBoardQuality data, EBoardQuality& r) { r = static_cast(data); } static_assert(size(EFlippedBoard()) == cHERMES_FLIPPED_BOARD_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EFlippedBoard d, EHermesFlippedBoard& r) { r = static_cast(d); } - inline void CToCpp(EHermesFlippedBoard d, EFlippedBoard& r) { r = static_cast(d); } + inline void CppToC(EFlippedBoard data, EHermesFlippedBoard& r) { r = static_cast(data); } + inline void CToCpp(EHermesFlippedBoard data, EFlippedBoard& r) { r = static_cast(data); } static_assert(size(ESubBoardState()) == cHERMES_SUB_BOARD_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ESubBoardState d, EHermesSubBoardState& r) { r = static_cast(d); } - inline void CToCpp(EHermesSubBoardState d, ESubBoardState& r) { r = static_cast(d); } + inline void CppToC(ESubBoardState data, EHermesSubBoardState& r) { r = static_cast(data); } + inline void CToCpp(EHermesSubBoardState data, ESubBoardState& r) { r = static_cast(data); } static_assert(size(ETransferState()) == cHERMES_TRANSFER_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ETransferState d, EHermesTransferState& r) { r = static_cast(d); } - inline void CToCpp(EHermesTransferState d, ETransferState& r) { r = static_cast(d); } + inline void CppToC(ETransferState data, EHermesTransferState& r) { r = static_cast(data); } + inline void CToCpp(EHermesTransferState data, ETransferState& r) { r = static_cast(data); } static_assert(size(ENotificationCode()) == cHERMES_NOTIFICATION_CODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ENotificationCode d, EHermesNotificationCode& r) { r = static_cast(d); } - inline void CToCpp(EHermesNotificationCode d, ENotificationCode& r) { r = static_cast(d); } + inline void CppToC(ENotificationCode data, EHermesNotificationCode& r) { r = static_cast(data); } + inline void CToCpp(EHermesNotificationCode data, ENotificationCode& r) { r = static_cast(data); } static_assert(size(ESeverity()) == cHERMES_SEVERITY_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ESeverity d, EHermesSeverity& r) { r = static_cast(d); } - inline void CToCpp(EHermesSeverity d, ESeverity& r) { r = static_cast(d); } + inline void CppToC(ESeverity data, EHermesSeverity& r) { r = static_cast(data); } + inline void CToCpp(EHermesSeverity data, ESeverity& r) { r = static_cast(data); } static_assert(size(ECheckState()) == cHERMES_CHECK_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckState d, EHermesCheckState& r) { r = static_cast(d); } - inline void CToCpp(EHermesCheckState d, ECheckState& r) { r = static_cast(d); } + inline void CppToC(ECheckState data, EHermesCheckState& r) { r = static_cast(data); } + inline void CToCpp(EHermesCheckState data, ECheckState& r) { r = static_cast(data); } static_assert(size(EErrorCode()) == cHERMES_ERROR_CODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EErrorCode d, EHermesErrorCode& r) { r = static_cast(d); } - inline void CToCpp(EHermesErrorCode d, EErrorCode& r) { r = static_cast(d); } + inline void CppToC(EErrorCode data, EHermesErrorCode& r) { r = static_cast(data); } + inline void CToCpp(EHermesErrorCode data, EErrorCode& r) { r = static_cast(data); } static_assert(size(ECheckAliveResponseMode()) == cHERMES_CHECK_ALIVE_RESPONSE_MODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckAliveResponseMode d, EHermesCheckAliveResponseMode& r) { r = static_cast(d); } - inline void CToCpp(EHermesCheckAliveResponseMode d, ECheckAliveResponseMode& r) { r = static_cast(d); } + inline void CppToC(ECheckAliveResponseMode data, EHermesCheckAliveResponseMode& r) { r = static_cast(data); } + inline void CToCpp(EHermesCheckAliveResponseMode data, ECheckAliveResponseMode& r) { r = static_cast(data); } static_assert(size(EBoardArrivedTransfer()) == cHERMES_BOARD_ARRIVED_TRANSFER_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardArrivedTransfer d, EHermesBoardArrivedTransfer& r) { r = static_cast(d); } - inline void CToCpp(EHermesBoardArrivedTransfer d, EBoardArrivedTransfer& r) { r = static_cast(d); } + inline void CppToC(EBoardArrivedTransfer data, EHermesBoardArrivedTransfer& r) { r = static_cast(data); } + inline void CToCpp(EHermesBoardArrivedTransfer data, EBoardArrivedTransfer& r) { r = static_cast(data); } static_assert(size(EBoardDepartedTransfer()) == cHERMES_BOARD_DEPARTED_TRANSFER_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardDepartedTransfer d, EHermesBoardDepartedTransfer& r) { r = static_cast(d); } - inline void CToCpp(EHermesBoardDepartedTransfer d, EBoardDepartedTransfer& r) { r = static_cast(d); } + inline void CppToC(EBoardDepartedTransfer data, EHermesBoardDepartedTransfer& r) { r = static_cast(data); } + inline void CToCpp(EHermesBoardDepartedTransfer data, EBoardDepartedTransfer& r) { r = static_cast(data); } static_assert(size(EReplyWorkOrderInfoStatus()) == cHERMES_REPLY_WORK_ORDER_INFO_STATUS_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EReplyWorkOrderInfoStatus d, EHermesReplyWorkOrderInfoStatus& r) { r = static_cast(d); } - inline void CToCpp(EHermesReplyWorkOrderInfoStatus d, EReplyWorkOrderInfoStatus& r) { r = static_cast(d); } + inline void CppToC(EReplyWorkOrderInfoStatus data, EHermesReplyWorkOrderInfoStatus& r) { r = static_cast(data); } + inline void CToCpp(EHermesReplyWorkOrderInfoStatus data, EReplyWorkOrderInfoStatus& r) { r = static_cast(data); } static_assert(size(EVerticalState()) == cHERMES_VERTICAL_STATE_ENUM_SIZE, "enum mismatch"); - inline EHermesVerticalState ToC(EVerticalState d) { return static_cast(d); } - inline EVerticalState ToCpp(EHermesVerticalState d) { return static_cast(d); } + inline EHermesVerticalState ToC(EVerticalState data) { return static_cast(data); } + inline EVerticalState ToCpp(EHermesVerticalState data) { return static_cast(data); } // ------------------------------------------------------------------------- - // Forward declarations + // Forward declarations — specialisations defined below // ------------------------------------------------------------------------- template struct Converter2C; template struct ConverterBase { ConverterBase() = default; - ConverterBase(const ConverterBase&) = delete; - ConverterBase(ConverterBase&&) = delete; + ConverterBase(const ConverterBase&) = delete; + ConverterBase(ConverterBase&&) = delete; ConverterBase& operator=(const ConverterBase&) = delete; ConverterBase& operator=(ConverterBase&&) = delete; + const T* CPointer() const { return &m_data; } T m_data{}; }; - // ------------------------------------------------------------------------- - // UpstreamConfiguration / DownstreamConfiguration - // ------------------------------------------------------------------------- + // UpstreamConfiguration inline void CppToC(const UpstreamConfiguration& data, HermesUpstreamConfiguration& result) { CppToC(data.m_upstreamLaneId, result.m_upstreamLaneId); @@ -236,6 +270,7 @@ namespace Hermes CToCpp(data.m_port, result.m_port); } + // DownstreamConfiguration inline void CppToC(const DownstreamConfiguration& data, HermesDownstreamConfiguration& result) { CppToC(data.m_downstreamLaneId, result.m_downstreamLaneId); @@ -251,18 +286,16 @@ namespace Hermes CToCpp(data.m_port, result.m_port); } - // ------------------------------------------------------------------------- // SetConfigurationData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const SetConfigurationData& data) { - CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_machineId, m_data.m_machineId); CppToC(data.m_optionalSupervisorySystemPort, m_data.m_pOptionalSupervisorySystemPort); - CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); - CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); + CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); + CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); } private: VectorHolder m_upstreamHolder; @@ -271,23 +304,19 @@ namespace Hermes inline SetConfigurationData ToCpp(const HermesSetConfigurationData& data) { SetConfigurationData result; - CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_machineId, result.m_machineId); CToCpp(data.m_pOptionalSupervisorySystemPort, result.m_optionalSupervisorySystemPort); - CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); - CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); + CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); + CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); return result; } - // ------------------------------------------------------------------------- // GetConfigurationData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const GetConfigurationData&) {} }; inline GetConfigurationData ToCpp(const HermesGetConfigurationData&) { return {}; } - // ------------------------------------------------------------------------- // CurrentConfigurationData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -295,8 +324,8 @@ namespace Hermes { CppToC(data.m_optionalMachineId, m_data.m_optionalMachineId); CppToC(data.m_optionalSupervisorySystemPort, m_data.m_pOptionalSupervisorySystemPort); - CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); - CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); + CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); + CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); } private: VectorHolder m_upstreamHolder; @@ -305,16 +334,14 @@ namespace Hermes inline CurrentConfigurationData ToCpp(const HermesCurrentConfigurationData& data) { CurrentConfigurationData result; - CToCpp(data.m_optionalMachineId, result.m_optionalMachineId); + CToCpp(data.m_optionalMachineId, result.m_optionalMachineId); CToCpp(data.m_pOptionalSupervisorySystemPort, result.m_optionalSupervisorySystemPort); - CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); - CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); + CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); + CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); return result; } - // ------------------------------------------------------------------------- // ConnectionInfo - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -334,10 +361,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- - // Error - // FIX: was Converter2C (typo) — corrected to Converter2C - // ------------------------------------------------------------------------- + // FIX: Was Converter2C — typo, must be Converter2C template<> struct Converter2C : ConverterBase { @@ -355,19 +379,17 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // CheckAliveData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const CheckAliveData& data) { - CppToC(data.m_optionalType, m_optType, m_data.m_pOptionalType); + CppToC(data.m_optionalType, m_optionalType, m_data.m_pOptionalType); CppToC(data.m_optionalId, m_data.m_optionalId); } private: - EHermesCheckAliveType m_optType{}; + EHermesCheckAliveType m_optionalType{}; }; inline CheckAliveData ToCpp(const HermesCheckAliveData& data) { @@ -377,9 +399,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // NotificationData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -399,19 +419,17 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // ServiceDescriptionData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const ServiceDescriptionData& data) { - CppToC(data.m_machineId, m_data.m_machineId); - CppToC(data.m_laneId, m_data.m_laneId); + CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_laneId, m_data.m_laneId); CppToC(data.m_optionalInterfaceId, m_data.m_optionalInterfaceId); CppToC(data.m_version, m_data.m_version); - m_data.m_pSupportedFeatures = nullptr; + m_data.m_pSupportedFeatures = nullptr; // features are optional, not mapped here } }; inline ServiceDescriptionData ToCpp(const HermesServiceDescriptionData& data) @@ -424,10 +442,13 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // SubBoard / SubBoards helper - // SubBoards is typedef'd as std::vector in HermesData.hpp - // ------------------------------------------------------------------------- + struct SubBoardsHolder + { + VectorHolder m_holder; + HermesSubBoards m_data{}; + }; + inline void CppToC(const SubBoard& data, HermesSubBoard& result) { CppToC(data.m_pos, result.m_pos); @@ -441,163 +462,141 @@ namespace Hermes CToCpp(data.m_st, result.m_st); } - struct SubBoardsHolder - { - VectorHolder m_holder; - HermesSubBoards m_data{}; - }; - inline void CppToC(const SubBoards& data, SubBoardsHolder& holder) + inline void CppToC(const std::vector& data, SubBoardsHolder& holder) { CppToC(data, holder.m_holder, holder.m_data); } - inline void CToCpp(const HermesSubBoards& data, SubBoards& result) + inline void CToCpp(const HermesSubBoards& data, std::vector& result) { CToCpp(data, result); } - // ------------------------------------------------------------------------- // BoardAvailableData - // FIX: field names corrected to match HermesData.hpp: - // m_length -> m_optionalLengthInMM - // m_width -> m_optionalWidthInMM - // etc. - // m_subBoards -> m_optionalSubBoards - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const BoardAvailableData& data) { - CppToC(data.m_boardId, m_data.m_boardId); - CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_flippedBoard, m_data.m_flippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_optionalLengthInMM, m_optLength, m_data.m_pOptionalLengthInMM); - CppToC(data.m_optionalWidthInMM, m_optWidth, m_data.m_pOptionalWidthInMM); - CppToC(data.m_optionalThicknessInMM, m_optThick, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_optionalTopClearanceHeightInMM, m_optTopCl, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_optionalBottomClearanceHeightInMM, m_optBotCl, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_optionalWeightInGrams, m_optWeight, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); - CppToC(data.m_optionalRoute, m_optRoute, m_data.m_pOptionalRoute); - CppToC(data.m_optionalAction, m_optAction, m_data.m_pOptionalAction); - CppToC(data.m_optionalSubBoards, m_subBoards); - m_data.m_optionalSubBoards = m_subBoards.m_data; + CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_flippedBoard, m_data.m_flippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_length, m_optLength, m_data.m_pOptionalLengthInMM); + CppToC(data.m_width, m_optWidth, m_data.m_pOptionalWidthInMM); + CppToC(data.m_thickness, m_optThick, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_topClearanceHeight, m_optTopClear, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_bottomClearanceHeight, m_optBotClear, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_weight, m_optWeight, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_optionalRoute, m_optRoute, m_data.m_pOptionalRoute); + CppToC(data.m_optionalAction, m_optAction, m_data.m_pOptionalAction); + CppToC(data.m_subBoards, m_subBoardsHolder); + m_data.m_optionalSubBoards = m_subBoardsHolder.m_data; } private: double m_optLength{}, m_optWidth{}, m_optThick{}, m_optSpeed{}; - double m_optTopCl{}, m_optBotCl{}, m_optWeight{}; + double m_optTopClear{}, m_optBotClear{}, m_optWeight{}; uint16_t m_optRoute{}, m_optAction{}; - SubBoardsHolder m_subBoards; + SubBoardsHolder m_subBoardsHolder; }; inline BoardAvailableData ToCpp(const HermesBoardAvailableData& data) { BoardAvailableData result; - CToCpp(data.m_boardId, result.m_boardId); - CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); - CToCpp(data.m_failedBoard, result.m_failedBoard); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_flippedBoard, result.m_flippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); - CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); - CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM,result.m_optionalBottomClearanceHeightInMM); - CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); + CToCpp(data.m_boardId, result.m_boardId); + CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); + CToCpp(data.m_failedBoard, result.m_failedBoard); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_flippedBoard, result.m_flippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_length); + CToCpp(data.m_pOptionalWidthInMM, result.m_width); + CToCpp(data.m_pOptionalThicknessInMM, result.m_thickness); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_topClearanceHeight); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_bottomClearanceHeight); + CToCpp(data.m_pOptionalWeightInGrams, result.m_weight); CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); CToCpp(data.m_pOptionalAction, result.m_optionalAction); - CToCpp(data.m_optionalSubBoards, result.m_optionalSubBoards); + CToCpp(data.m_optionalSubBoards, result.m_subBoards); return result; } - // ------------------------------------------------------------------------- // RevokeBoardAvailableData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const RevokeBoardAvailableData&) {} }; inline RevokeBoardAvailableData ToCpp(const HermesRevokeBoardAvailableData&) { return {}; } - // ------------------------------------------------------------------------- // MachineReadyData - // FIX: field names corrected (m_length -> m_optionalLengthInMM, etc.) - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const MachineReadyData& data) { - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalForecastId, m_data.m_optionalForecastId); - CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_optionalFlippedBoard, m_optFlipped, m_data.m_pOptionalFlippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_optionalLengthInMM, m_optLength, m_data.m_pOptionalLengthInMM); - CppToC(data.m_optionalWidthInMM, m_optWidth, m_data.m_pOptionalWidthInMM); - CppToC(data.m_optionalThicknessInMM, m_optThick, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_optionalTopClearanceHeightInMM, m_optTopCl, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_optionalBottomClearanceHeightInMM,m_optBotCl, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_optionalWeightInGrams, m_optWeight, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalForecastId, m_data.m_optionalForecastId); + CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_optionalFlippedBoard, m_optFlipped, m_data.m_pOptionalFlippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_length, m_optLength, m_data.m_pOptionalLengthInMM); + CppToC(data.m_width, m_optWidth, m_data.m_pOptionalWidthInMM); + CppToC(data.m_thickness, m_optThick, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_topClearanceHeight, m_optTopClear, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_bottomClearanceHeight, m_optBotClear, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_weight, m_optWeight, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); } private: EHermesFlippedBoard m_optFlipped{}; double m_optLength{}, m_optWidth{}, m_optThick{}, m_optSpeed{}; - double m_optTopCl{}, m_optBotCl{}, m_optWeight{}; + double m_optTopClear{}, m_optBotClear{}, m_optWeight{}; }; inline MachineReadyData ToCpp(const HermesMachineReadyData& data) { MachineReadyData result; - CToCpp(data.m_failedBoard, result.m_failedBoard); - CToCpp(data.m_optionalForecastId, result.m_optionalForecastId); - CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); - CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); - CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM,result.m_optionalBottomClearanceHeightInMM); - CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); - CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + CToCpp(data.m_failedBoard, result.m_failedBoard); + CToCpp(data.m_optionalForecastId, result.m_optionalForecastId); + CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_length); + CToCpp(data.m_pOptionalWidthInMM, result.m_width); + CToCpp(data.m_pOptionalThicknessInMM, result.m_thickness); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_topClearanceHeight); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM,result.m_bottomClearanceHeight); + CToCpp(data.m_pOptionalWeightInGrams, result.m_weight); + CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); return result; } - // ------------------------------------------------------------------------- // RevokeMachineReadyData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const RevokeMachineReadyData&) {} }; inline RevokeMachineReadyData ToCpp(const HermesRevokeMachineReadyData&) { return {}; } - // ------------------------------------------------------------------------- // StartTransportData - // FIX: field name corrected: m_conveyorSpeed -> m_optionalConveyorSpeedInMMPerSecs - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { explicit Converter2C(const StartTransportData& data) { - CppToC(data.m_boardId, m_data.m_boardId); - CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); } private: double m_optSpeed{}; @@ -606,13 +605,11 @@ namespace Hermes { StartTransportData result; CToCpp(data.m_boardId, result.m_boardId); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); return result; } - // ------------------------------------------------------------------------- // StopTransportData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -630,9 +627,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // TransportFinishedData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -650,9 +645,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // QueryBoardInfoData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -670,13 +663,14 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // CommandData - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { - explicit Converter2C(const CommandData& data) { CppToC(data.m_command, m_data.m_command); } + explicit Converter2C(const CommandData& data) + { + CppToC(data.m_command, m_data.m_command); + } }; inline CommandData ToCpp(const HermesCommandData& data) { @@ -685,9 +679,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // UpstreamSettings - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -715,9 +707,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // DownstreamSettings - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -745,9 +735,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // ConfigurationServiceSettings - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -765,9 +753,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // VerticalServiceSettings - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -791,9 +777,7 @@ namespace Hermes return result; } - // ------------------------------------------------------------------------- // VerticalClientSettings - // ------------------------------------------------------------------------- template<> struct Converter2C : ConverterBase { @@ -819,4 +803,4 @@ namespace Hermes return result; } -} // namespace Hermes \ No newline at end of file +} // namespace Hermes diff --git a/src/include/HermesOptional.hpp b/src/include/HermesOptional.hpp index b7a5c1a..1f04d8d 100644 --- a/src/include/HermesOptional.hpp +++ b/src/include/HermesOptional.hpp @@ -16,15 +16,11 @@ limitations under the License. // Copyright (c) ASM Assembly Systems GmbH & Co. KG // -// FIX: Replaced the custom Optional class with std::optional. -// The library requires C++17 (enforced in CMakeLists.txt), so std::optional -// is always available. -// -// FIX: Added operator<< for std::optional in the Hermes namespace. -// The internal implementation files (AsioServer.cpp etc.) use BuildString() -// which calls operator<< on every argument. The old custom Optional had -// operator<< defined on it. std::optional does not. Adding it here restores -// that behaviour without modifying any internal .cpp files. +// FIX: The original file rolled its own Optional class to avoid C++11/Boost +// dependencies. Since this library now requires C++17 (enforced in CMakeLists), +// we use std::optional directly. This removes the custom implementation entirely +// and fixes the two-argument constructor mismatch that caused compile errors in +// HermesDataConversion.hpp (Optional{data.m_pData, data.m_size}). // #pragma once @@ -33,20 +29,7 @@ limitations under the License. namespace Hermes { - // Drop-in alias — all code using Hermes::Optional compiles unchanged. + // Drop-in alias — all code using Hermes::Optional continues to compile. template using Optional = std::optional; - - // operator<< for Hermes::Optional (= std::optional). - // Required by internal StringBuilder usage in AsioServer.cpp and friends. - // Prints the contained value if present, or "" if not. - template - S& operator<<(S& s, const std::optional& o) - { - if (o.has_value()) - s << *o; - else - s << ""; - return s; - } -} \ No newline at end of file +} From f254edc9c61259e31d514745d79104009728f040 Mon Sep 17 00:00:00 2001 From: sahil agarwal Date: Fri, 27 Mar 2026 14:28:36 +0530 Subject: [PATCH 12/14] Revert "modern hermes fix" This reverts commit 1d3f062eaced9e0eb227ca86d18467279539c35f. --- README.md | 1003 ++----------------- docs/01_Architecture_Overview.md | 45 + docs/02_Building_and_Linking.md | 52 + docs/03_Modern_API_Basics.md | 55 + docs/04_Downstream_Receiver.md | 0 docs/05_Upstream_Sender.md | 72 ++ docs/06_Core_Message_Types.md | 72 ++ docs/07_Network_and_Topology.md | 67 ++ docs/08_Legacy_Interface_API.md | 93 ++ docs/09_Vertical_MES_Integration.md | 86 ++ docs/BUILDING.md | 100 ++ examples/interactive_sender.cpp | 75 ++ examples/machine_b_pnp.cpp | 68 ++ examples/machine_c_oven.cpp | 41 + examples/rpi1_upstream.cpp | 206 ++-- examples/rpi2_downstream.cpp | 209 ++-- src/include/HermesDataConversion.hpp | 1392 ++++++++++++++++++-------- src/include/HermesModern.hpp | 811 ++++++++------- src/include/HermesOptional.hpp | 92 +- src/include/HermesStringView.h | 25 +- src/include/HermesStringView.hpp | 113 ++- 21 files changed, 2802 insertions(+), 1875 deletions(-) create mode 100644 docs/01_Architecture_Overview.md create mode 100644 docs/02_Building_and_Linking.md create mode 100644 docs/03_Modern_API_Basics.md create mode 100644 docs/04_Downstream_Receiver.md create mode 100644 docs/05_Upstream_Sender.md create mode 100644 docs/06_Core_Message_Types.md create mode 100644 docs/07_Network_and_Topology.md create mode 100644 docs/08_Legacy_Interface_API.md create mode 100644 docs/09_Vertical_MES_Integration.md create mode 100644 docs/BUILDING.md create mode 100644 examples/interactive_sender.cpp create mode 100644 examples/machine_b_pnp.cpp create mode 100644 examples/machine_c_oven.cpp diff --git a/README.md b/README.md index d54faca..2a90fba 100644 --- a/README.md +++ b/README.md @@ -1,912 +1,93 @@ -# Hermes C++ Library — Complete Reference - -**Protocol:** The Hermes Standard — vendor-independent machine-to-machine communication for SMT assembly lines -**License:** Apache 2.0 -**Requires:** C++17, Boost 1.66+, CMake 3.15+ - ---- - -## Table of Contents - -1. [What is Hermes](#1-what-is-hermes) -2. [Architecture overview](#2-architecture-overview) -3. [Building the library](#3-building-the-library) -4. [Header map — what to include](#4-header-map) -5. [Core types reference](#5-core-types-reference) -6. [Modern C++ API — HermesModern.hpp](#6-modern-c-api) -7. [Low-level C++ API — Hermes.hpp](#7-low-level-c-api) -8. [Serialization API — HermesSerialization.hpp](#8-serialization-api) -9. [Configuration service](#9-configuration-service) -10. [Vertical interface](#10-vertical-interface) -11. [Complete examples](#11-complete-examples) -12. [Bugs fixed in this version](#12-bugs-fixed) - ---- - -## 1. What is Hermes - -Hermes is an open TCP/IP + XML protocol that connects machines in an electronics assembly line. Each machine has an **Upstream** port (faces the previous machine) and a **Downstream** port (faces the next machine). PCB boards flow from Upstream to Downstream along the lane. - -``` -[Machine A] --downstream:50100--> [Machine B] --downstream:50101--> [Machine C] - <--upstream:50100--- <--upstream:50101--- -``` - -The Downstream machine **listens** on a port. The Upstream machine **connects** to it. So: - -- `Hermes::Downstream` / `Modern::Downstream` — **server role**, listens for incoming connections -- `Hermes::Upstream` / `Modern::Upstream` — **client role**, connects to the downstream machine - ---- - -## 2. Architecture overview +Hermes C++ Library (lib_cpp) +The Hermes Standard (IPC-HERMES-9852) is the modern, non-proprietary TCP/IP and XML-based successor to the legacy SMEMA standard. It enables vendor-independent machine-to-machine communication in SMT assembly lines. This repository provides the official, high-performance C++ implementation of the protocol. -``` -Your application - | - |--- HermesModern.hpp (std::function callbacks — recommended) - |--- Hermes.hpp (virtual interface callbacks — advanced) - |--- HermesSerialization (XML serialize/deserialize) - | - +---> Hermes C API (Hermes.h / HermesData.h) - | compiled into libhermes.so - | - +---> Boost.Asio (networking) - +---> pugixml (XML parsing) -``` - -### Message flow — horizontal (machine to machine) - -``` -Downstream machine Upstream machine -(Modern::Downstream) (Modern::Upstream) - | | - |<-- TCP connect -------------------- | - | | - |<-- ServiceDescription ------------- | (Upstream identifies itself) - |--> ServiceDescription -----------> | (Downstream identifies itself) - | | - |<-- MachineReady ------------------- | (Upstream ready to receive) - |--> BoardAvailable --------------> | (Downstream has a board) - | | - |<-- StartTransport ----------------- | (Upstream says: send it) - | [board physically moves] | - |--> TransportFinished -----------> | (Downstream confirms done) - | | - |<-- StopTransport ---------------- | (Upstream confirms received) -``` - -### Who receives what - -| You create | You listen for (receive) | You send | -|-------------------|-------------------------------------------------------------------|-------------------------------------------------------| -| `Modern::Downstream` | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | -| `Modern::Upstream` | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | - ---- - -## 3. Building the library - -### Prerequisites - -```bash -# Debian / Ubuntu / Raspberry Pi OS -sudo apt install -y g++ cmake libboost-all-dev - -# macOS -brew install cmake boost - -# Windows -# Install Boost via vcpkg: vcpkg install boost -``` - -### Build - -```bash -git clone https://github.com/hermes-org/lib_cpp.git -cd lib_cpp -mkdir build && cd build -cmake .. -make -j4 -``` - -This produces `build/src/Hermes/libhermes.so` (Linux/macOS) or `hermes.dll` (Windows). - -### Compile your application - -```bash -g++ -std=c++17 -o myapp myapp.cpp \ - -I./src/include \ - -L./build/src/Hermes -lhermes \ - -lboost_system -lpthread - -# Run (Linux — tell the linker where to find the .so) -LD_LIBRARY_PATH=./build/src/Hermes ./myapp -``` - ---- - -## 4. Header map - -| Header | What it gives you | When to include it | -|--------|------------------|--------------------| -| `HermesModern.hpp` | `Modern::Downstream`, `Modern::Upstream` with std::function callbacks | **Start here. Recommended for all new code.** | -| `Hermes.hpp` | `Hermes::Downstream`, `Hermes::Upstream`, virtual callback interfaces | When you need session IDs, raw XML, or fine-grained control | -| `HermesData.hpp` | All C++ data structs, enums, settings structs | Included automatically by the above | -| `HermesSerialization.hpp` | `ToXml()`, `FromXml()` | When you need to inspect or log raw XML messages | -| `HermesOptional.hpp` | `Hermes::Optional` = `std::optional` | Included automatically | -| `HermesStringView.hpp` | `Hermes::StringView` = `std::string_view` | Included automatically | - ---- - -## 5. Core types reference - -### EState — connection state machine - -```cpp -enum class EState { - eNOT_CONNECTED, // No TCP connection - eSOCKET_CONNECTED, // TCP connected, waiting for ServiceDescription - eSERVICE_DESCRIPTION_DOWNSTREAM, // ServiceDescription exchanged - eNOT_AVAILABLE_NOT_READY, // Connected, neither side ready - eBOARD_AVAILABLE, // Downstream has a board waiting - eMACHINE_READY, // Upstream is ready to receive - eAVAILABLE_AND_READY, // Both sides ready — transport can start - eTRANSPORTING, // Board is moving - eTRANSPORT_STOPPED, // Transport paused - eTRANSPORT_FINISHED, // Board delivered - eDISCONNECTED // Connection closed -}; -``` - -### ETraceType — log levels - -```cpp -enum class ETraceType { - eSENT, // Raw XML sent over the wire - eRECEIVED, // Raw XML received - eDEBUG, - eINFO, - eWARNING, - eERROR -}; -``` - -### Error - -```cpp -struct Error { - EErrorCode m_code; // eSUCCESS, eNETWORK_ERROR, eTIMEOUT, etc. - std::string m_text; // Human-readable description - - explicit operator bool() const; // true if error (code != eSUCCESS) -}; -``` - -### ConnectionInfo - -```cpp -struct ConnectionInfo { - std::string m_address; // Remote IP address - uint16_t m_port; // Remote port - std::string m_hostName; // Remote hostname (if resolvable) -}; -``` - -### ServiceDescriptionData - -Exchanged in both directions at connection start. Identifies the machine. - -```cpp -struct ServiceDescriptionData { - std::string m_machineId; // Required. Your machine identifier. - uint32_t m_laneId; // Required. Which lane (1-based). - Optional m_optionalInterfaceId; // Optional. Interface label. - std::string m_version; // Protocol version string. -}; -``` - -### BoardAvailableData - -Sent by Downstream to signal a board is ready for transfer. - -```cpp -struct BoardAvailableData { - std::string m_boardId; // Required. Unique board identifier. - std::string m_boardIdCreatedBy; // Required. Machine that created the ID. - EBoardQuality m_failedBoard; // eANY, eGOOD, eBAD - Optional m_optionalProductTypeId; - EFlippedBoard m_flippedBoard; // eSIDE_UP_IS_UNKNOWN, eTOP_SIDE_IS_UP, eBOTTOM_SIDE_IS_UP - Optional m_optionalTopBarcode; - Optional m_optionalBottomBarcode; - Optional m_length; // mm - Optional m_width; // mm - Optional m_thickness; // mm - Optional m_conveyorSpeed; // mm/s - Optional m_topClearanceHeight; // mm - Optional m_bottomClearanceHeight; // mm - Optional m_weight; // grams - Optional m_optionalWorkOrderId; - Optional m_optionalBatchId; - Optional m_optionalRoute; - Optional m_optionalAction; - std::vector m_subBoards; -}; -``` - -### MachineReadyData - -Sent by Upstream to signal it is ready to receive a board. - -```cpp -struct MachineReadyData { - EBoardQuality m_failedBoard; // What quality board it can accept - Optional m_optionalForecastId; - Optional m_optionalBoardId; - Optional m_optionalProductTypeId; - Optional m_optionalFlippedBoard; - Optional m_optionalTopBarcode; - Optional m_optionalBottomBarcode; - Optional m_length; - Optional m_width; - Optional m_thickness; - Optional m_conveyorSpeed; - Optional m_topClearanceHeight; - Optional m_bottomClearanceHeight; - Optional m_weight; - Optional m_optionalWorkOrderId; - Optional m_optionalBatchId; -}; -``` - -### StartTransportData / StopTransportData / TransportFinishedData - -```cpp -struct StartTransportData { - std::string m_boardId; // Which board to transport - Optional m_conveyorSpeed; // mm/s, optional override -}; - -struct StopTransportData { - ETransferState m_transferState; // eCOMPLETE, eINCOMPLETE, eNOT_STARTED - std::string m_boardId; -}; - -struct TransportFinishedData { - ETransferState m_transferState; - std::string m_boardId; -}; -``` - -### NotificationData - -Sent by either side to report errors or status. - -```cpp -struct NotificationData { - ENotificationCode m_notificationCode; // ePROTOCOL_ERROR, eMACHINE_SHUTDOWN, etc. - ESeverity m_severity; // eFATAL, eERROR, eWARNING, eINFO - std::string m_description; // Human-readable message -}; -``` - -### Settings structs - -```cpp -struct DownstreamSettings { - std::string m_machineId; // Required - Optional m_optionalClientAddress; // Restrict to one client IP - uint16_t m_port{0}; // 0 = use cBASE_PORT (50100) - double m_checkAlivePeriodInSeconds{60}; - double m_reconnectWaitTimeInSeconds{10}; - ECheckAliveResponseMode m_checkAliveResponseMode{eAUTO}; - ECheckState m_checkState{eSEND_AND_RECEIVE}; -}; - -struct UpstreamSettings { - std::string m_machineId; // Required - std::string m_hostAddress; // Required. IP of the downstream machine. - uint16_t m_port{0}; // 0 = use cBASE_PORT (50100) - double m_checkAlivePeriodInSeconds{60}; - double m_reconnectWaitTimeInSeconds{10}; - ECheckAliveResponseMode m_checkAliveResponseMode{eAUTO}; - ECheckState m_checkState{eSEND_AND_RECEIVE}; -}; -``` - ---- - -## 6. Modern C++ API - -`HermesModern.hpp` provides `Modern::Downstream` and `Modern::Upstream`. These wrap the low-level virtual callback interface with `std::function` callbacks. This is the recommended API for new code. - -### Modern::Downstream - -```cpp -Hermes::Modern::Downstream ds(unsigned laneId); -``` - -**Register callbacks:** - -```cpp -// Connection events — sessionId identifies the active connection -ds.RegisterConnectedCallback( [](unsigned sessionId, const ConnectionInfo& info) { ... }); -ds.RegisterDisconnectedCallback([](unsigned sessionId, const Error& err) { ... }); -ds.RegisterStateChangeCallback( [](unsigned sessionId, EState state) { ... }); -ds.RegisterTraceCallback( [](unsigned sessionId, ETraceType type, StringView msg) { ... }); - -// Messages received FROM the Upstream machine -ds.RegisterServiceDescriptionCallback( [](unsigned sessionId, EState, const ServiceDescriptionData& d) { ... }); -ds.RegisterMachineReadyCallback( [](unsigned sessionId, EState, const MachineReadyData& d) { ... }); -ds.RegisterRevokeMachineReadyCallback( [](unsigned sessionId, EState, const RevokeMachineReadyData& d) { ... }); -ds.RegisterStartTransportCallback( [](unsigned sessionId, EState, const StartTransportData& d) { ... }); -ds.RegisterStopTransportCallback( [](unsigned sessionId, EState, const StopTransportData& d) { ... }); -ds.RegisterQueryBoardInfoCallback( [](unsigned sessionId, const QueryBoardInfoData& d) { ... }); - -// Auxiliary -ds.RegisterNotificationCallback([](unsigned sessionId, const NotificationData& d) { ... }); -ds.RegisterCheckAliveCallback( [](unsigned sessionId, const CheckAliveData& d) { ... }); -ds.RegisterCommandCallback( [](unsigned sessionId, const CommandData& d) { ... }); -``` - -**Lifecycle:** - -```cpp -DownstreamSettings settings; -settings.m_machineId = "MyMachine"; -settings.m_port = 50100; - -ds.Enable(settings); // starts listening, launches network thread -// ... application runs ... -ds.Stop(); // stops network thread, closes connection -``` - -**Send messages to the Upstream:** - -```cpp -// Must call from inside Post() or from within a callback — both are on the network thread -ds.Post([&ds, sessionId]() { - BoardAvailableData board; - board.m_boardId = "BOARD-001"; - board.m_boardIdCreatedBy = "MyMachine"; - board.m_failedBoard = EBoardQuality::eGOOD; - board.m_flippedBoard = EFlippedBoard::eTOP_SIDE_IS_UP; - ds.Signal(sessionId, board); -}); -``` - -### Modern::Upstream - -```cpp -Hermes::Modern::Upstream us(unsigned laneId); -``` - -**Register callbacks:** - -```cpp -us.RegisterConnectedCallback( [](unsigned sessionId, const ConnectionInfo& info) { ... }); -us.RegisterDisconnectedCallback([](unsigned sessionId, const Error& err) { ... }); -us.RegisterStateChangeCallback( [](unsigned sessionId, EState state) { ... }); -us.RegisterTraceCallback( [](unsigned sessionId, ETraceType type, StringView msg) { ... }); - -// Messages received FROM the Downstream machine -us.RegisterServiceDescriptionCallback( [](unsigned sessionId, EState, const ServiceDescriptionData& d) { ... }); -us.RegisterBoardAvailableCallback( [](unsigned sessionId, EState, const BoardAvailableData& d) { ... }); -us.RegisterRevokeBoardAvailableCallback( [](unsigned sessionId, EState, const RevokeBoardAvailableData& d) { ... }); -us.RegisterTransportFinishedCallback( [](unsigned sessionId, EState, const TransportFinishedData& d) { ... }); -us.RegisterBoardForecastCallback( [](unsigned sessionId, EState, const BoardForecastData& d) { ... }); -us.RegisterSendBoardInfoCallback( [](unsigned sessionId, const SendBoardInfoData& d) { ... }); - -// Auxiliary -us.RegisterNotificationCallback([](unsigned sessionId, const NotificationData& d) { ... }); -us.RegisterCheckAliveCallback( [](unsigned sessionId, const CheckAliveData& d) { ... }); -us.RegisterCommandCallback( [](unsigned sessionId, const CommandData& d) { ... }); -``` - -**Lifecycle:** - -```cpp -UpstreamSettings settings; -settings.m_machineId = "MyMachine"; -settings.m_hostAddress = "192.168.1.2"; // IP of the Downstream machine -settings.m_port = 50100; - -us.Enable(settings); -// ... -us.Stop(); -``` - -**Send messages to the Downstream:** - -```cpp -us.Post([&us, sessionId]() { - MachineReadyData ready; - ready.m_failedBoard = EBoardQuality::eANY; - us.Signal(sessionId, ready); -}); -``` - ---- - -## 7. Low-level C++ API - -`Hermes.hpp` provides `Hermes::Downstream` and `Hermes::Upstream` with virtual callback interfaces. Use this when you need the session ID in every callback, raw XML signals, or multiple sessions. - -### Implementing IDownstreamCallback - -```cpp -struct MyDownstreamHandler : Hermes::IDownstreamCallback -{ - // Pure virtuals — must implement all of these: - void OnConnected(unsigned sessionId, EState, const ConnectionInfo&) override; - void OnDisconnected(unsigned sessionId, EState, const Error&) override; - void OnState(unsigned sessionId, EState) override; - void OnTrace(unsigned sessionId, ETraceType, StringView) override; - - // Messages from Upstream — pure virtual: - void On(unsigned sessionId, EState, const ServiceDescriptionData&) override; - void On(unsigned sessionId, EState, const MachineReadyData&) override; - void On(unsigned sessionId, EState, const RevokeMachineReadyData&) override; - void On(unsigned sessionId, EState, const StartTransportData&) override; - void On(unsigned sessionId, EState, const StopTransportData&) override; - void On(unsigned sessionId, const NotificationData&) override; - void On(unsigned sessionId, const CommandData&) override; - - // Optional — have default empty implementations: - void On(unsigned sessionId, const CheckAliveData&) override {} - void On(unsigned sessionId, const QueryBoardInfoData&) override {} -}; -``` - -**Usage:** - -```cpp -MyDownstreamHandler handler; -Hermes::Downstream downstream(1, handler); // lane 1 - -DownstreamSettings settings; -settings.m_machineId = "Machine-A"; -settings.m_port = 50100; -downstream.Enable(settings); -downstream.Run(); // blocks until Stop() is called from another thread -``` - -### Implementing IUpstreamCallback - -```cpp -struct MyUpstreamHandler : Hermes::IUpstreamCallback -{ - void OnConnected(unsigned sessionId, EState, const ConnectionInfo&) override; - void OnDisconnected(unsigned sessionId, EState, const Error&) override; - void OnState(unsigned sessionId, EState) override; - void OnTrace(unsigned sessionId, ETraceType, StringView) override; - - // Messages from Downstream — pure virtual: - void On(unsigned sessionId, EState, const ServiceDescriptionData&) override; - void On(unsigned sessionId, EState, const BoardAvailableData&) override; - void On(unsigned sessionId, EState, const RevokeBoardAvailableData&) override; - void On(unsigned sessionId, EState, const TransportFinishedData&) override; - void On(unsigned sessionId, const NotificationData&) override; - void On(unsigned sessionId, const CommandData&) override; - - // Optional: - void On(unsigned sessionId, const CheckAliveData&) override {} - void On(unsigned sessionId, EState, const BoardForecastData&) override {} - void On(unsigned sessionId, const SendBoardInfoData&) override {} -}; -``` - -### Sending signals from within callbacks - -Always use `Post()` to send signals — it schedules the send on the Hermes network thread safely: - -```cpp -void On(unsigned sessionId, EState, const MachineReadyData&) override -{ - // DO NOT call Signal() directly here in the virtual callback API. - // Use Post() to dispatch onto the network thread: - m_downstream->Post([this, sessionId]() { - BoardAvailableData board; - board.m_boardId = "B-001"; - board.m_boardIdCreatedBy = "MachineA"; - board.m_failedBoard = EBoardQuality::eGOOD; - board.m_flippedBoard = EFlippedBoard::eTOP_SIDE_IS_UP; - m_downstream->Signal(sessionId, board); - }); -} -``` - -> In `Modern::Downstream` / `Modern::Upstream`, callbacks already run on the network thread, so you can call `Signal()` directly inside them. - ---- - -## 8. Serialization API - -`HermesSerialization.hpp` lets you convert any data struct to XML and back. Useful for logging, testing, or debugging. - -```cpp -#include "HermesSerialization.hpp" - -// Serialize to XML string -BoardAvailableData board; -board.m_boardId = "BOARD-001"; -board.m_boardIdCreatedBy = "Machine-A"; -board.m_failedBoard = EBoardQuality::eGOOD; -board.m_flippedBoard = EFlippedBoard::eTOP_SIDE_IS_UP; - -std::string xml = Hermes::ToXml(board); -// xml = "BOARD-001..." - -// Deserialize from XML string -Hermes::Optional result = Hermes::FromXml(xml); -if (result.has_value()) { - std::cout << result->m_boardId << "\n"; // "BOARD-001" -} -``` - -All message types are supported: - -```cpp -Hermes::ToXml(const ServiceDescriptionData&) -Hermes::ToXml(const BoardAvailableData&) -Hermes::ToXml(const RevokeBoardAvailableData&) -Hermes::ToXml(const MachineReadyData&) -Hermes::ToXml(const RevokeMachineReadyData&) -Hermes::ToXml(const StartTransportData&) -Hermes::ToXml(const StopTransportData&) -Hermes::ToXml(const TransportFinishedData&) -Hermes::ToXml(const BoardForecastData&) -Hermes::ToXml(const QueryBoardInfoData&) -Hermes::ToXml(const SendBoardInfoData&) -Hermes::ToXml(const NotificationData&) -Hermes::ToXml(const CheckAliveData&) -Hermes::ToXml(const SetConfigurationData&) -Hermes::ToXml(const CurrentConfigurationData&) -Hermes::ToXml(const GetConfigurationData&) -Hermes::ToXml(const CommandData&) - -// Vertical: -Hermes::ToXml(const SupervisoryServiceDescriptionData&) -Hermes::ToXml(const BoardArrivedData&) -Hermes::ToXml(const BoardDepartedData&) -Hermes::ToXml(const QueryWorkOrderInfoData&) -Hermes::ToXml(const SendWorkOrderInfoData&) -Hermes::ToXml(const ReplyWorkOrderInfoData&) -Hermes::ToXml(const QueryHermesCapabilitiesData&) -Hermes::ToXml(const SendHermesCapabilitiesData&) -``` - ---- - -## 9. Configuration service - -The configuration service allows a remote tool (e.g. a factory MES) to read and write the machine's lane configuration over TCP on port 1248. - -```cpp -#include "Hermes.hpp" - -struct MyConfigHandler : Hermes::IConfigurationServiceCallback -{ - void OnConnected(unsigned sessionId, const ConnectionInfo& info) override - { - std::cout << "Config client connected: " << info.m_address << "\n"; - } - - // Remote client reads our configuration - CurrentConfigurationData OnGetConfiguration(unsigned, const ConnectionInfo&) override - { - CurrentConfigurationData config; - config.m_optionalMachineId = "Machine-A"; - - UpstreamConfiguration up; - up.m_upstreamLaneId = 1; - up.m_hostAddress = "192.168.1.2"; - up.m_port = 50100; - config.m_upstreamConfigurations.push_back(up); - - return config; - } - - // Remote client writes a new configuration — return Error{} for success - Error OnSetConfiguration(unsigned, const ConnectionInfo&, - const SetConfigurationData& newConfig) override - { - // Apply newConfig to your machine... - return Error{}; // success - } - - void OnDisconnected(unsigned, const Error&) override {} - void OnTrace(unsigned, ETraceType, StringView) override {} -}; - -// Usage: -MyConfigHandler handler; -Hermes::ConfigurationService service(handler); - -ConfigurationServiceSettings settings; -settings.m_port = cCONFIG_PORT; // 1248 - -service.Enable(settings); -service.Run(); // blocks -``` - -### Remote configuration client - -Query or set a machine's configuration from another process: - -```cpp -// Get configuration -auto [config, error] = Hermes::GetConfiguration("192.168.1.2", 10, nullptr); -if (!error) { - std::cout << "Machine: " << config.m_optionalMachineId.value_or("unknown") << "\n"; -} - -// Set configuration -SetConfigurationData newConfig; -newConfig.m_machineId = "Machine-A"; - -Error err = Hermes::SetConfiguration( - "192.168.1.2", // host - newConfig, // configuration to write - 10, // timeout seconds - nullptr, // optional: resulting config out - nullptr, // optional: notifications out - nullptr // optional: trace callback -); -``` - ---- - -## 10. Vertical interface - -The vertical interface connects a machine to a supervisory system (MES/ERP) on a separate TCP port. `VerticalService` is the machine side. `VerticalClient` is the supervisory system side. - -```cpp -struct MyVerticalHandler : Hermes::IVerticalServiceCallback -{ - void OnConnected(unsigned sessionId, EVerticalState, const ConnectionInfo&) override {} - - void On(unsigned sessionId, EVerticalState, - const SupervisoryServiceDescriptionData& data) override - { - std::cout << "Supervisor connected: " << data.m_systemId << "\n"; - } - - void On(unsigned sessionId, const GetConfigurationData&, const ConnectionInfo&) override {} - void On(unsigned sessionId, const SetConfigurationData&, const ConnectionInfo&) override {} - void On(unsigned sessionId, const NotificationData&) override {} - void On(unsigned sessionId, const QueryHermesCapabilitiesData&) override {} - void OnDisconnected(unsigned sessionId, EVerticalState, const Error&) override {} - void OnTrace(unsigned sessionId, ETraceType, StringView) override {} -}; -``` - ---- - -## 11. Complete examples - -### Example 1 — Downstream machine (listens, receives a board) - -```cpp -#include "HermesModern.hpp" -#include -#include -#include -#include -#include - -static std::atomic g_running{true}; - -int main() -{ - std::signal(SIGINT, [](int) { g_running = false; }); - - Hermes::Modern::Downstream ds(1); // lane 1 - - // --- Connection events --- - ds.RegisterConnectedCallback([](unsigned sessionId, const Hermes::ConnectionInfo& info) { - std::cout << "[DS] Upstream connected from " << info.m_address << "\n"; - }); - - ds.RegisterDisconnectedCallback([](unsigned sessionId, const Hermes::Error& err) { - std::cout << "[DS] Upstream disconnected\n"; - }); - - ds.RegisterStateChangeCallback([](unsigned sessionId, Hermes::EState state) { - std::cout << "[DS] State: " << state << "\n"; - }); - - // --- Receive ServiceDescription from Upstream, reply with ours --- - ds.RegisterServiceDescriptionCallback( - [&ds](unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) { - std::cout << "[DS] Received ServiceDescription from: " << data.m_machineId << "\n"; - - // Reply with our own ServiceDescription - ds.Post([&ds, sessionId]() { - Hermes::ServiceDescriptionData reply; - reply.m_machineId = "Machine-Downstream"; - reply.m_laneId = 1; - ds.Signal(sessionId, reply); - std::cout << "[DS] Sent ServiceDescription\n"; - }); - }); - - // --- When Upstream is MachineReady, send BoardAvailable --- - ds.RegisterMachineReadyCallback( - [&ds](unsigned sessionId, Hermes::EState, const Hermes::MachineReadyData&) { - std::cout << "[DS] Upstream is MachineReady — sending BoardAvailable\n"; - - ds.Post([&ds, sessionId]() { - Hermes::BoardAvailableData board; - board.m_boardId = "PCB-2024-001"; - board.m_boardIdCreatedBy = "Machine-Downstream"; - board.m_failedBoard = Hermes::EBoardQuality::eGOOD; - board.m_flippedBoard = Hermes::EFlippedBoard::eTOP_SIDE_IS_UP; - ds.Signal(sessionId, board); - }); - }); - - // --- When Upstream starts transport, confirm when done --- - ds.RegisterStartTransportCallback( - [&ds](unsigned sessionId, Hermes::EState, const Hermes::StartTransportData& data) { - std::cout << "[DS] StartTransport for board: " << data.m_boardId << "\n"; - - // Simulate board moving - std::this_thread::sleep_for(std::chrono::seconds(1)); - - ds.Post([&ds, sessionId, boardId = data.m_boardId]() { - Hermes::TransportFinishedData finished; - finished.m_transferState = Hermes::ETransferState::eCOMPLETE; - finished.m_boardId = boardId; - ds.Signal(sessionId, finished); - std::cout << "[DS] TransportFinished sent\n"; - }); - }); - - // --- Notifications --- - ds.RegisterNotificationCallback([](unsigned, const Hermes::NotificationData& n) { - std::cout << "[DS] Notification: " << n.m_description << "\n"; - }); - - // --- Start --- - Hermes::DownstreamSettings settings; - settings.m_machineId = "Machine-Downstream"; - settings.m_port = 50100; - ds.Enable(settings); - - std::cout << "[DS] Listening on port 50100. Ctrl+C to stop.\n"; - while (g_running) - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - ds.Stop(); - return 0; -} -``` - -### Example 2 — Upstream machine (connects, sends MachineReady) - -```cpp -#include "HermesModern.hpp" -#include -#include -#include -#include -#include - -static std::atomic g_running{true}; - -int main() -{ - std::signal(SIGINT, [](int) { g_running = false; }); - - Hermes::Modern::Upstream us(1); // lane 1 - - us.RegisterConnectedCallback([](unsigned sessionId, const Hermes::ConnectionInfo& info) { - std::cout << "[US] Connected to downstream at " << info.m_address << "\n"; - }); - - us.RegisterDisconnectedCallback([](unsigned sessionId, const Hermes::Error& err) { - std::cout << "[US] Disconnected\n"; - }); - - us.RegisterStateChangeCallback([](unsigned sessionId, Hermes::EState state) { - std::cout << "[US] State: " << state << "\n"; - }); - - // --- Receive ServiceDescription, reply and send MachineReady --- - us.RegisterServiceDescriptionCallback( - [&us](unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) { - std::cout << "[US] Received ServiceDescription from: " << data.m_machineId << "\n"; - - us.Post([&us, sessionId]() { - // Send our ServiceDescription - Hermes::ServiceDescriptionData reply; - reply.m_machineId = "Machine-Upstream"; - reply.m_laneId = 1; - us.Signal(sessionId, reply); - - // Then send MachineReady - Hermes::MachineReadyData ready; - ready.m_failedBoard = Hermes::EBoardQuality::eANY; - us.Signal(sessionId, ready); - std::cout << "[US] Sent ServiceDescription + MachineReady\n"; - }); - }); - - // --- Receive BoardAvailable, trigger StartTransport --- - us.RegisterBoardAvailableCallback( - [&us](unsigned sessionId, Hermes::EState, const Hermes::BoardAvailableData& board) { - std::cout << "[US] BoardAvailable: " << board.m_boardId << "\n"; - - us.Post([&us, sessionId, boardId = board.m_boardId]() { - Hermes::StartTransportData start; - start.m_boardId = boardId; - us.Signal(sessionId, start); - std::cout << "[US] StartTransport sent\n"; - }); - }); - - // --- Receive TransportFinished, send StopTransport --- - us.RegisterTransportFinishedCallback( - [&us](unsigned sessionId, Hermes::EState, const Hermes::TransportFinishedData& data) { - std::cout << "[US] TransportFinished for board: " << data.m_boardId << "\n"; - - us.Post([&us, sessionId, boardId = data.m_boardId]() { - Hermes::StopTransportData stop; - stop.m_transferState = Hermes::ETransferState::eCOMPLETE; - stop.m_boardId = boardId; - us.Signal(sessionId, stop); - std::cout << "[US] StopTransport sent — full board transfer complete\n"; - }); - }); - - us.RegisterNotificationCallback([](unsigned, const Hermes::NotificationData& n) { - std::cout << "[US] Notification: " << n.m_description << "\n"; - }); - - // EDIT THIS — set the downstream machine's IP: - Hermes::UpstreamSettings settings; - settings.m_machineId = "Machine-Upstream"; - settings.m_hostAddress = "192.168.1.2"; // <-- downstream machine IP - settings.m_port = 50100; - us.Enable(settings); - - std::cout << "[US] Connecting to 192.168.1.2:50100. Ctrl+C to stop.\n"; - while (g_running) - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - - us.Stop(); - return 0; -} -``` - -### Example 3 — XML serialization and inspection - -```cpp -#include "HermesSerialization.hpp" -#include - -int main() -{ - // Serialize - Hermes::BoardAvailableData board; - board.m_boardId = "PCB-001"; - board.m_boardIdCreatedBy = "Machine-A"; - board.m_failedBoard = Hermes::EBoardQuality::eGOOD; - board.m_flippedBoard = Hermes::EFlippedBoard::eTOP_SIDE_IS_UP; - board.m_length = 150.0; - board.m_width = 100.0; - - std::string xml = Hermes::ToXml(board); - std::cout << "XML:\n" << xml << "\n"; - - // Deserialize - auto result = Hermes::FromXml(xml); - if (result.has_value()) { - std::cout << "BoardId: " << result->m_boardId << "\n"; - if (result->m_length.has_value()) - std::cout << "Length: " << *result->m_length << "mm\n"; - } - return 0; -} -``` \ No newline at end of file +1. Core Repository Map +To navigate the library effectively, refer to the following directory structure: + +src/include/: The Public API. This directory contains the headers required to integrate Hermes into your application. + +Hermes.hpp: The primary Object-Oriented C++ wrapper. + +Hermes.h: The low-level C-style API. + +src/Hermes/: The Implementation. Contains the core logic, including the ASIO networking stack, XML serialization, and the standard-compliant state machines. + +test/BoostTestHermes/: Verification Suite. Contains comprehensive unit and integration tests. This is the source of truth for protocol-compliant behavior. + +References/: External Headers. Contains local versions of pugixml and boost headers necessary for compilation in restricted environments. + +2. Technical Requirements +Dependencies +C++ Standard: C++17 or higher. + +Networking: Boost.ASIO (v1.66 through v1.78 recommended). + +Note: Versions 1.87+ require the -DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT flag. + +XML Processing: pugixml (included in References/). + +Platform Independence +The library is designed to be fully platform-independent. It supports: + +Windows: MSVC (2017+) and MinGW-w64. + +Linux: GCC (7+) and Clang. + +Build System: The project utilizes CMake (3.15+) to generate native build files (Visual Studio Solutions or Makefiles) for your specific platform. + +3. Unified Build Process (CMake) +To build the library on any platform, use the following standard workflow: + +Generate: mkdir build && cd build && cmake .. + +Build: cmake --build . --config Release + +Outputs: + +hermes.dll / hermes.lib (Windows) + +libhermes.so (Linux) + +4. API Architecture & Design +The Callback Model +The C++ API follows a strict Interface-Based Callback pattern. + +Users must implement classes inheriting from IDownstreamCallback (Receivers) or IUpstreamCallback (Senders). + +All pure virtual methods in these interfaces must be overridden to handle protocol events (Connection, Disconnection, Board Available, Machine Ready, etc.). + +Execution Model +Blocking Event Loop: The core processing occurs within the Run() method. + +Threading: Because Run() blocks the calling thread to process network I/O, it must be executed in a dedicated background thread to maintain application responsiveness. + +State Management: The library handles all internal state transitions (e.g., Not Connected -> Service Description -> Not Ready -> Ready) automatically based on the Enable() configuration. + +5. Supported Communication Channels +The library implements the four primary channels defined by IPC-HERMES-9852: + +Upstream: Machine-to-Machine (Sending). + +Downstream: Machine-to-Machine (Receiving). + +Configuration: Exchange of machine capabilities and line settings. + +Vertical: Communication with factory-level MES/ERP systems. + +6. Deployment Topologies +The library supports standard IPv4 networking across various hardware setups: + +Point-to-Point: Direct Ethernet connection between two machines. + +Switched Fabric: Deployment via factory-wide network switches. This is the preferred method for modern "Smart Factory" environments, as it allows a single network interface to handle both horizontal (machine) and vertical (MES) data simultaneously. + +7. Quality Assurance +All protocol features are verified against the BoostTestHermes suite. + +Unit Tests: Verify individual XML serialization and data structures. + +Integration Tests: Simulate full handshakes between virtual Upstream and Downstream sessions. + +Automation: Compatible with ctest for continuous integration workflows. + +License: Copyright (c) ASM Assembly Systems GmbH & Co. KG. Licensed under the Apache License, Version 2.0. See COPYRIGHT.txt for details. \ No newline at end of file diff --git a/docs/01_Architecture_Overview.md b/docs/01_Architecture_Overview.md new file mode 100644 index 0000000..a0533f5 --- /dev/null +++ b/docs/01_Architecture_Overview.md @@ -0,0 +1,45 @@ +# 01. Architecture Overview + +The Hermes Standard (IPC-HERMES-9852) is a protocol based on TCP/IP and XML designed to replace the legacy SMEMA wiring standard. This C++ library serves as a robust wrapper around the protocol, handling the low-level socket connections, XML serialization, and the strict state machines required for compliance. + +To effectively use this library, you must understand three core architectural concepts: **Machine Roles**, the **Event-Driven Model**, and **Thread Management**. + +--- + +## 1. Machine Roles (Network Topology) + +In a Hermes line, machines communicate horizontally (machine-to-machine) and vertically (machine-to-factory). The library provides dedicated classes for each role: + +### Horizontal Integration (The SMT Line) +* **`Downstream` (The Receiver):** Acts as a **TCP Server**. A downstream machine (e.g., a Pick & Place) opens a specific port and listens. It waits for the previous machine in the line to connect and hand over a board. +* **`Upstream` (The Sender):** Acts as a **TCP Client**. An upstream machine (e.g., a Printer) actively connects to the IP address and port of the next machine in the line to send a board. + +*Note: Most machines in the middle of a line will instantiate **both** an `Upstream` object (to send to the right) and a `Downstream` object (to receive from the left).* + +### Vertical Integration (The Factory) +* **`ConfigurationService`:** Used to exchange machine capabilities and supervisory line control data. +* **`VerticalService`:** Acts as a TCP Server to provide real-time board tracking data to higher-level MES/ERP systems (often working alongside IPC-CFX). + +--- + +## 2. The Event-Driven Model + +The library is entirely asynchronous and event-driven. You do not write code that says `WaitForBoard()`. Instead, you register **Callbacks** that the library triggers when network events occur. + +If you are using the modern wrapper (`HermesModern.hpp`), these events are handled via standard C++ lambdas: + +```cpp +// Example: The library triggers this lambda when an XML message arrives +downstream.RegisterBoardAvailableCallback([](const Hermes::BoardAvailableData& board) { + std::cout << "Board Arrived! Barcode: " << board._topBarcode << std::endl; +}); +3. Thread Management (Critical) +Because the library utilizes Boost.ASIO for high-performance networking, it relies on a continuous event loop to process incoming socket data. + +The Run() Loop: All network processing happens inside the Run() method of the Upstream or Downstream objects. + +Blocking Behavior: The Run() method is blocking. If you call it on your main application thread, your application's UI or main control loop will freeze. + +The Solution: You must always execute the core Hermes loop in a dedicated background std::thread. + +(Note: If you use the HermesModern.hpp wrapper, this background thread is automatically managed for you when you call Enable()). \ No newline at end of file diff --git a/docs/02_Building_and_Linking.md b/docs/02_Building_and_Linking.md new file mode 100644 index 0000000..d55b451 --- /dev/null +++ b/docs/02_Building_and_Linking.md @@ -0,0 +1,52 @@ +# 02. Building and Linking + +The Hermes library uses **CMake (3.15+)** as its universal build system, ensuring seamless, cross-platform compilation on Windows, Linux, and macOS. + +--- + +## 1. System Requirements + +Before you begin, verify your environment has the following: +* **C++17 Compiler:** MSVC 2017+, GCC 7+, or Clang 5+. +* **Boost Libraries:** Version 1.66 through 1.78 is recommended. The library specifically requires `boost_system` and `boost_thread`. + * *Note on Boost 1.87+: If using a newer version of Boost where `io_service` is deprecated, our CMake setup automatically applies the `-DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT` flag to ensure it still compiles.* +* **pugixml:** No installation required. The required files are bundled locally in the `References/` folder. + +--- + +## 2. Compiling the Library + +Open a terminal in the root of the cloned repository and execute a standard out-of-source build: + +```bash +mkdir build && cd build + +# 1. Configure the project (CMake locates Boost and your compiler) +cmake .. + +# 2. Compile the core library +cmake --build . --config Release +Build Outputs: + +Windows: build/Release/hermes.dll and hermes.lib + +Linux: build/libhermes.so + +3. Linking Hermes to Your Project +The cleanest way to integrate Hermes into your own C++ application is by using CMake's add_subdirectory command. + +Assuming you cloned this repository into a folder named third_party/lib_cpp inside your project, your application's CMakeLists.txt would look like this: + +CMake +cmake_minimum_required(VERSION 3.15) +project(MySmtMachine) + +# 1. Include the Hermes library directory +add_subdirectory(third_party/lib_cpp) + +# 2. Define your application executable +add_executable(MyApp main.cpp) + +# 3. Link Hermes to your application +# This automatically configures the include paths for Hermes.hpp +target_link_libraries(MyApp PRIVATE hermes) \ No newline at end of file diff --git a/docs/03_Modern_API_Basics.md b/docs/03_Modern_API_Basics.md new file mode 100644 index 0000000..34b5836 --- /dev/null +++ b/docs/03_Modern_API_Basics.md @@ -0,0 +1,55 @@ +# 03. Modern API Basics + +The legacy Hermes C++ library relies on strict class inheritance (`IDownstreamCallback` and `IUpstreamCallback`) and manual thread management. To simplify development, we provide `HermesModern.hpp`, a wrapper that allows you to use modern C++17 lambdas and automatically manages the background networking threads. + +--- + +## 1. Instantiating the Node + +Every Hermes connection requires a `laneId` (typically `1` for a standard single-lane machine). You instantiate either a `Downstream` (Receiver) or `Upstream` (Sender) object from the `Hermes::Modern` namespace. + +```cpp +#include "HermesModern.hpp" + +// Create a receiver (listens for the previous machine) on Lane 1 +Hermes::Modern::Downstream receiver(1); + +// Create a sender (connects to the next machine) on Lane 1 +Hermes::Modern::Upstream sender(1); +2. Registering Callbacks +Instead of overriding pure virtual functions in a separate class, you register lambda functions directly to the object. You only need to register the callbacks your machine actually cares about. + +C++ +// Handle successful TCP connections +receiver.RegisterConnectedCallback([](const Hermes::ConnectionInfo& info) { + std::cout << "Connected to: " << info.m_hostAddress << "\n"; +}); + +// Handle incoming board data +receiver.RegisterBoardAvailableCallback([](const Hermes::BoardAvailableData& board) { + std::cout << "Incoming Board ID: " << board.m_boardId << "\n"; +}); + +// Handle disconnection or errors +receiver.RegisterDisconnectedCallback([](const Hermes::Error& err) { + std::cout << "Disconnected. Reason: " << err.m_text << "\n"; +}); +3. Configuration and Execution (Enable) +To start listening (Downstream) or actively connecting (Upstream), you must populate a settings object and pass it to the Enable() method. + +Crucially, calling Enable() in the modern wrapper automatically spawns the required background std::thread to process network events. You do not need to manage the blocking Run() loop yourself. + +C++ +Hermes::DownstreamSettings settings; +settings.m_machineId = "My_Pick_And_Place"; +settings.m_clientAddress = "192.168.1.100"; // Accept connections from this IP +settings.m_port = 50101; // Standard Hermes Default Port + +// Start the network event loop in the background +receiver.Enable(settings); +4. Shutting Down (Stop) +When your application is closing, or if you need to sever the connection to change line configurations, call Stop(). This will cleanly disconnect the socket, send the appropriate XML termination messages, and safely join the background thread. + +C++ +// Safely shut down the connection and stop the background thread +receiver.Stop(); \ No newline at end of file diff --git a/docs/04_Downstream_Receiver.md b/docs/04_Downstream_Receiver.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/05_Upstream_Sender.md b/docs/05_Upstream_Sender.md new file mode 100644 index 0000000..fda4ee4 --- /dev/null +++ b/docs/05_Upstream_Sender.md @@ -0,0 +1,72 @@ +# 05. Upstream Sender (The Handshake) + +An **Upstream** node acts as the sending machine (e.g., a printer sending a board to a pick-and-place). It operates as a TCP Client, actively attempting to connect to the IP address and port of the next machine in the line. + +To successfully hand over a board, you must follow the exact reverse of the Downstream handshake. + +--- + +## 1. The Standard Handshake Sequence + +If you send messages out of sequence, the receiving machine is required by the Hermes Standard to drop the connection. + +| Upstream (Sender - YOU) | Direction | Downstream (Receiver) | +| :--- | :---: | :--- | +| `ServiceDescription` | ➔ | *(Establish protocol version)* | +| | ⬅ | `MachineReady` *(I have space for a board)* | +| `BoardAvailable` | ➔ | *(Contains Barcode/Dimensions)* | +| | ⬅ | `StartTransport` *(Conveyors turning, send it!)* | +| `TransportFinished`| ➔ | *(Board has left my machine)* | +| | ⬅ | `StopTransport` *(Board is fully inside me)* | + +--- + +## 2. Implementing the Sender Logic + +Using the `HermesModern.hpp` wrapper, here is how you implement a compliant sender. + +*(Note: In a real machine application, you will also integrate your physical sensors—like board edge detectors—into this logic flow).* + +```cpp +#include "HermesModern.hpp" +#include + +Hermes::Modern::Upstream sender(1); +const unsigned CURRENT_SESSION = 1; + +// 1. Wait for the Downstream machine to say it is empty +sender.RegisterMachineReadyCallback([&](const Hermes::MachineReadyData& data) { + std::cout << "Downstream is ready to receive a board.\n"; + + // 2. When your machine finishes processing a board, announce it + Hermes::BoardAvailableData boardData; + boardData.m_boardId = "PCB_12345"; + boardData.m_topBarcode = "SN-998877"; + boardData.m_lengthInMM = 150.0f; + boardData.m_widthInMM = 100.0f; + sender.Signal(CURRENT_SESSION, boardData); +}); + +// 3. Downstream has started its conveyors and says "Send it" +sender.RegisterStartTransportCallback([&](const Hermes::StartTransportData& data) { + std::cout << "Downstream conveyors running at " << data.m_conveyorSpeed << " mm/s\n"; + + // 4. Start YOUR conveyors to physically move the board out. + // ... (Wait for hardware sensor to confirm board left the machine) ... + + // 5. Tell the Downstream machine the board is fully on their side + Hermes::TransportFinishedData finishedData; + finishedData.m_transferState = Hermes::ETransferState::eCOMPLETE; + sender.Signal(CURRENT_SESSION, finishedData); +}); + +// 6. Downstream confirms the board has safely arrived inside +sender.RegisterStopTransportCallback([&](const Hermes::StopTransportData& data) { + std::cout << "Handover Complete! Board safely delivered.\n"; + + // You are now free to process the next board. +}); +3. Handling Exceptions +RevokeMachineReady: If you receive this before sending BoardAvailable, it means the downstream machine suddenly became unavailable (e.g., an operator hit the Emergency Stop). You must wait until you receive a new MachineReady message before trying to send your board. + +Connection Loss: If the connection drops during transport, halt your conveyors immediately. \ No newline at end of file diff --git a/docs/06_Core_Message_Types.md b/docs/06_Core_Message_Types.md new file mode 100644 index 0000000..376b066 --- /dev/null +++ b/docs/06_Core_Message_Types.md @@ -0,0 +1,72 @@ +# 06. Core Message Types & Data Dictionary + +The Hermes Standard defines specific payloads for every stage of the handshake. This document provides a quick reference for the exact C++ structs and fields you will interact with in `HermesData.hpp`. + +## ⚠️ Important Note on `Hermes::Optional` +To maintain compatibility with older C++ compilers, this library does not use `std::optional`. It uses a custom `Hermes::Optional`. To safely read optional fields like barcodes or dimensions, check them like a boolean and dereference them with `*`: + +```cpp +if (boardData.m_optionalTopBarcode) { + std::string barcode = *boardData.m_optionalTopBarcode; +} +1. Initialization Messages +ServiceDescriptionData +Exchanged immediately upon TCP connection to verify compatibility. + +std::string m_machineId: The name/ID of the machine. + +unsigned m_laneId: The specific lane (usually 0 or 1). + +std::string m_version: Protocol version (Defaults to "1.5"). + +SupportedFeatures m_supportedFeatures: Flags for advanced features. + +2. Board Handover Messages +BoardAvailableData (Sent by Upstream) +Announces a board is waiting to be sent. Contains physical properties. +Required Fields: + +std::string m_boardId: Unique UUID for the board. + +std::string m_boardIdCreatedBy: Machine ID that generated the UUID. + +EBoardQuality m_failedBoard: eANY, eGOOD, or eBAD. + +EFlippedBoard m_flippedBoard: eSIDE_UP_IS_UNKNOWN, eTOP_SIDE_IS_UP, eBOTTOM_SIDE_IS_UP. + +Common Optional Fields: + +Optional m_optionalTopBarcode / m_optionalBottomBarcode + +Optional m_optionalLengthInMM / m_optionalWidthInMM / m_optionalThicknessInMM + +Optional m_optionalWorkOrderId / m_optionalBatchId + +MachineReadyData (Sent by Downstream) +Announces the receiver is empty and ready. + +EBoardQuality m_failedBoard: Specifies what quality the machine accepts (e.g., eGOOD). + +Note: This struct also contains the same optional physical dimensions as BoardAvailableData. Downstream machines can populate these to demand specific board sizes. + +3. Transport Control Messages +StartTransportData (Sent by Downstream) +Commands the Upstream machine to begin pushing the board. + +std::string m_boardId: The UUID of the board being requested. + +Optional m_optionalConveyorSpeedInMMPerSecs: The speed the sender should match. + +TransportFinishedData (Sent by Upstream) +Confirms the board has entirely left the sender's conveyor. + +ETransferState m_transferState: Usually ETransferState::eCOMPLETE. + +std::string m_boardId: The UUID of the transferred board. + +StopTransportData (Sent by Downstream) +Confirms the board has securely arrived inside the receiver. + +ETransferState m_transferState: Usually ETransferState::eCOMPLETE. + +std::string m_boardId: The UUID of the received board. \ No newline at end of file diff --git a/docs/07_Network_and_Topology.md b/docs/07_Network_and_Topology.md new file mode 100644 index 0000000..dcc16e5 --- /dev/null +++ b/docs/07_Network_and_Topology.md @@ -0,0 +1,67 @@ +# 07. Network Configuration & Topology + +Hermes operates over standard TCP/IP Ethernet. Because it uses standard networking rather than proprietary cables, machines can be wired together in different ways depending on the factory's infrastructure. + +This document explains the physical setups and how to configure your `UpstreamSettings` and `DownstreamSettings` structs to match them. + +--- + +## 1. Physical Topologies + +### Point-to-Point (Direct Connection) +This is the simplest setup and the direct equivalent of legacy SMEMA. Two adjacent machines are connected directly to each other using a standard Ethernet cable. +* **Pros:** Extremely secure; no interference from other factory traffic. +* **Cons:** Requires the machine to have multiple network interface cards (NICs) if it also needs to talk to the factory network (MES). + +### Switched Fabric (Smart Factory) +All machines in the line plug into a central factory Ethernet switch. +* **Pros:** A single physical cable handles both horizontal (machine-to-machine) and vertical (machine-to-MES) data. +* **Cons:** Requires strict IP filtering to ensure Machine A doesn't accidentally send a board to Machine C. + +--- + +## 2. The Hermes Port Standard + +By definition (IPC-HERMES-9852), the TCP port used for a connection is derived from the Lane ID. +* The Base Port is always **50100**. +* Port = Base Port + Lane ID. +* **Therefore, a standard single-lane machine ALWAYS communicates on Port 50101.** + +--- + +## 3. Configuring the Receiver (`DownstreamSettings`) + +The Receiver acts as a TCP Server. It opens a port and waits. + +```cpp +Hermes::DownstreamSettings settings; +settings.m_machineId = "Oven_01"; +settings.m_port = 50101; + +// OPTIONAL BUT RECOMMENDED IN SWITCHED NETWORKS: +// If you leave this blank, ANY machine on the factory floor can connect to your receiver. +// By setting this, you force the socket to ONLY accept connections from the specific +// IP address of the machine physically positioned before you in the line. +settings.m_optionalClientAddress = "192.168.1.50"; + +// Advanced keep-alive timings (defaults are usually fine) +settings.m_checkAlivePeriodInSeconds = 60.0; +settings.m_reconnectWaitTimeInSeconds = 10.0; +4. Configuring the Sender (UpstreamSettings) +The Sender acts as a TCP Client. It actively reaches out across the network to find the next machine. + +C++ +Hermes::UpstreamSettings settings; +settings.m_machineId = "Printer_01"; +settings.m_port = 50101; + +// REQUIRED: +// You must explicitly tell the sender the IP address of the Downstream +// receiver it is supposed to push boards into. +settings.m_hostAddress = "192.168.1.51"; + +// Advanced keep-alive timings +settings.m_checkAlivePeriodInSeconds = 60.0; +settings.m_reconnectWaitTimeInSeconds = 10.0; +5. Network Resilience +The library handles network drops automatically. If a cable is unplugged, the OnDisconnected callback will fire. The Upstream node will then automatically attempt to reconnect to the m_hostAddress every m_reconnectWaitTimeInSeconds (default: 10 seconds) until the connection is restored. \ No newline at end of file diff --git a/docs/08_Legacy_Interface_API.md b/docs/08_Legacy_Interface_API.md new file mode 100644 index 0000000..00306d7 --- /dev/null +++ b/docs/08_Legacy_Interface_API.md @@ -0,0 +1,93 @@ +# 08. Legacy Interface API (Advanced) + +While the `HermesModern.hpp` wrapper simplifies development using lambdas, advanced users may prefer to interact directly with the raw C++ library to minimize overhead or to integrate Hermes deeply into an existing class hierarchy. + +To use the raw API, you must inherit from the core interfaces and manage the blocking event loops manually. + +--- + +## 1. The Core Interfaces + +The library provides two primary abstract classes: +* `Hermes::IDownstreamCallback` +* `Hermes::IUpstreamCallback` + +If you inherit directly from these, your compiler will force you to implement every pure virtual method (e.g., `OnConnected`, `OnState`, and the various overloaded `On` methods for data). + +--- + +## 2. The Naming Collision Problem + +In a real factory, most machines sit in the middle of the line. This means your application class will likely need to act as both a Sender and a Receiver. + +If you try to inherit from both base interfaces simultaneously, you will encounter **ambiguous function names**. For example, both interfaces define this exact method: +`virtual void On(unsigned sessionId, EState state, const ServiceDescriptionData& data) = 0;` + +The compiler won't know if you are receiving a service description from the previous machine or the next machine. + +--- + +## 3. The "Helper" Solution + +To solve the naming collision, the library provides two specialized helper structs in `Hermes.hpp`: +* `Hermes::UpstreamCallbackHelper` +* `Hermes::DownstreamCallbackHelper` + +These helpers internally catch the ambiguous `On()` methods and reroute them to uniquely named functions like `OnUpstream(...)` and `OnDownstream(...)`. + +--- + +## 4. Legacy Implementation Example + +Here is how you use the helpers to build a machine that sends and receives simultaneously using the raw API: + +```cpp +#include "Hermes.hpp" +#include +#include + +// 1. Inherit from both Helpers, NOT the base interfaces +class MySmtMachine : public Hermes::UpstreamCallbackHelper, + public Hermes::DownstreamCallbackHelper +{ +public: + // 2. Instantiate the core objects, passing `*this` as the callback reference + MySmtMachine() : m_upstream(1, *this), m_downstream(1, *this) {} + + void Start() { + // 3. YOU must manage the blocking threads manually + m_upThread = std::thread([this]() { m_upstream.Run(); }); + m_downThread = std::thread([this]() { m_downstream.Run(); }); + + Hermes::UpstreamSettings upSettings("MyMachine", "192.168.1.51", 50101); + Hermes::DownstreamSettings downSettings("MyMachine", 50101); + + m_upstream.Enable(upSettings); + m_downstream.Enable(downSettings); + } + + void Stop() { + m_upstream.Stop(); + m_downstream.Stop(); + if (m_upThread.joinable()) m_upThread.join(); + if (m_downThread.joinable()) m_downThread.join(); + } + +protected: + // 4. Implement the uniquely named virtual methods + void OnUpstreamConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { + std::cout << "Sender Connected to next machine!\n"; + } + + void OnDownstreamConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { + std::cout << "Receiver Accepted connection from previous machine!\n"; + } + + // ... You must implement all remaining OnUpstream and OnDownstream virtuals ... + +private: + Hermes::Upstream m_upstream; + Hermes::Downstream m_downstream; + std::thread m_upThread; + std::thread m_downThread; +}; \ No newline at end of file diff --git a/docs/09_Vertical_MES_Integration.md b/docs/09_Vertical_MES_Integration.md new file mode 100644 index 0000000..4788afb --- /dev/null +++ b/docs/09_Vertical_MES_Integration.md @@ -0,0 +1,86 @@ +# 09. Vertical Integration (MES/ERP) + +While `Upstream` and `Downstream` handle **Horizontal** communication (machine passing boards to machine), the Hermes Standard also defines a **Vertical** channel. This allows higher-level factory software (like an MES, ERP, or line controller) to monitor and control the machines in real-time. + +In the ASM `lib_cpp` library, this is handled by the `VerticalService` and `VerticalClient` classes. + +--- + +## 1. The Architecture + +In a standard smart factory setup: +* **The Machine (You):** Acts as the **`VerticalService`** (TCP Server). You open a port (often `1248` or `50100`) and wait. +* **The MES/Factory Cloud:** Acts as the **`VerticalClient`** (TCP Client). It connects to every machine on the line to gather data. + +--- + +## 2. The Vertical Handshake + +When an MES connects to your machine, you must exchange supervisory descriptions. This is entirely separate from the machine-to-machine horizontal handshake. + +1. **MES Connects:** Your `OnConnected` callback fires. +2. **Exchange Descriptions:** The MES sends its `SupervisoryServiceDescriptionData`. Your machine responds with its own, detailing what features it supports (e.g., `FeatureBoardTracking`, `FeatureQueryWorkOrderInfo`). + +--- + +## 3. Real-Time Board Tracking + +The most common use case for Vertical Integration is letting the factory know exactly where every PCB is located. If the MES enables `FeatureBoardTracking`, your machine is responsible for firing off two specific messages during its operation: + +### A. `BoardArrivedData` +You must signal this to the `VerticalService` the moment a board successfully enters your machine. +**Key Fields:** +* `m_machineId`: Your machine's ID. +* `m_boardId`: The UUID of the board that just entered. +* `m_boardTransfer`: How it got there (e.g., `EBoardArrivedTransfer::eTRANSFERRED` from an upstream machine, or `eLOADED` if an operator manually placed it). + +### B. `BoardDepartedData` +You must signal this the moment a board completely leaves your machine. +**Key Fields:** +* `m_machineId`: Your machine's ID. +* `m_boardId`: The UUID of the board. +* `m_boardTransfer`: How it left (e.g., `EBoardDepartedTransfer::eTRANSFERRED` to a downstream machine, or `eREMOVED` if an operator took it out). + +--- + +## 4. Advanced: Work Orders and Routing + +If your machine supports dynamic recipes or routing, the Vertical channel allows you to ask the MES what to do with a board. + +1. **Query:** When a board arrives, your machine sends a `QueryWorkOrderInfoData` containing the board's barcode. +2. **Reply:** The MES responds with `ReplyWorkOrderInfoData`, telling your machine which recipe/program to load for that specific barcode. +3. **Route:** The MES can also inject `m_route` attributes, telling a sorting conveyor whether to pass the board through, send it to an inspection queue, or reject it to a scrap bin. + +--- + +## 5. Raw Implementation Example + +If you are using the raw C++ interface (bypassing lambdas), your vertical implementation will look like this: + +```cpp +#include "Hermes.hpp" + +class MyLineMonitor : public Hermes::IVerticalServiceCallback { +public: + MyLineMonitor() : m_verticalService(*this) {} + + void Start() { + Hermes::VerticalServiceSettings settings("My_Machine", 1248); + m_verticalService.Enable(settings); + // Ensure you run m_verticalService.Run() in a background thread! + } + + // --- Override the pure virtuals --- + void OnConnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::ConnectionInfo& info) override { + // MES has connected to us + } + + void On(unsigned sessionId, Hermes::EVerticalState state, const Hermes::SupervisoryServiceDescriptionData& data) override { + // MES told us who it is. We can now reply with our capabilities. + } + + // ... Implement remaining virtuals ... + +private: + Hermes::VerticalService m_verticalService; +}; \ No newline at end of file diff --git a/docs/BUILDING.md b/docs/BUILDING.md new file mode 100644 index 0000000..36d59b1 --- /dev/null +++ b/docs/BUILDING.md @@ -0,0 +1,100 @@ +# Building the Hermes C++ Library + +The Hermes library uses **CMake** as its universal build system. The library has been modernized to use **Header-Only Boost** for its core networking, making it incredibly lightweight and easy to compile natively on Windows, Linux, and macOS without complex binary linking. + +--- + +## 1. Prerequisites + +Before building, ensure your environment has the following installed: + +* **C++17 Compiler**: + * **Linux**: GCC 7+ or Clang 5+ + * **Windows**: MSYS2/MinGW-w64 (GCC) or MSVC (Visual Studio 2017+) + * **macOS**: Apple Clang +* **CMake**: Version 3.15 or higher. +* **Boost Libraries**: Version 1.66 or newer. *(Note: The core library only requires Boost headers. No compiled `.lib` or `.so` Boost binaries are required unless you are building the Test Suite).* +* **pugixml**: Included locally in the `References/` directory, so no external installation is required. + +--- + +## 2. Environment Setup (From Scratch) + +Depending on your operating system, follow the steps below to ensure you have all required build tools present before running CMake. + +### 🐧 Linux (Ubuntu / Debian) +Linux is the easiest platform to build on. Simply install the standard build essentials and Boost headers from your package manager: +```bash +sudo apt update && sudo apt upgrade -y +sudo apt install -y g++ cmake libboost-all-dev git +🪟 Windows (via MSYS2 / MinGW - Recommended) +To build a lightweight .dll using GCC on Windows, use MSYS2 (UCRT64 environment). Open your MSYS2 terminal and install the toolchain: + +Bash +pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-make mingw-w64-ucrt-x86_64-boost +Crucial: If you build from a standard Windows Command Prompt (cmd) instead of the MSYS2 terminal, you must temporarily add the MSYS2 binaries to your path before running CMake: + +DOS +set PATH=C:\msys64\ucrt64\bin;%PATH% +🪟 Windows (via Visual Studio / MSVC) +If you prefer Microsoft Visual Studio, ensure the "Desktop development with C++" workload is installed. You will also need to download Boost headers (e.g., from boost.org) and extract them to a local directory (e.g., C:\local\boost_1_84_0). + +3. Standard Build Process +The CMake workflow is generally identical regardless of your operating system. Open a terminal in the root of the cloned repository. + +Step 1: Create a Build Directory +Always perform an "out-of-source" build to keep your source tree clean. + +Bash +mkdir build +cd build +Step 2: Configure the Project +Run CMake to generate the build files. + +For Linux / macOS: + +Bash +cmake .. +make -j4 +For Windows (MSYS2 / MinGW): + +DOS +cmake .. -G "MinGW Makefiles" +For Windows (Visual Studio): + +DOS +cmake .. -DBOOST_ROOT="C:/local/boost_1_84_0" +Step 3: Compile the Library +Execute the build command to compile the library. + +Bash +cmake --build . --config Release +Build Outputs +Once the build completes successfully, the compiled binaries will be located in the build/src/Hermes/ directory: + +Windows: hermes.dll + +Linux: libhermes.so + +macOS: libhermes.dylib + +4. Building and Running the Tests +The repository includes a test suite (BoostTestHermes) to verify protocol compliance. + +Note: While the core library is header-only, building the tests requires the compiled boost_unit_test_framework binary. + +To run the tests from inside your build directory: + +Bash +ctest --output-on-failure -C Release +Alternatively, you can run the executable directly: + +Windows: .\test\BoostTestHermes\Release\BoostTestHermes.exe + +Linux: ./test/BoostTestHermes/BoostTestHermes + +To compile any file run +g++ -std=c++17 -o example.exe example.cpp \ + -I~/lib_cpp/src/include \ + -L~/lib_cpp/build -lhermes \ + -lboost_system -lpthread \ No newline at end of file diff --git a/examples/interactive_sender.cpp b/examples/interactive_sender.cpp new file mode 100644 index 0000000..5e48413 --- /dev/null +++ b/examples/interactive_sender.cpp @@ -0,0 +1,75 @@ +#include +#include +#include "HermesModern.hpp" + +void PrintMenu() { + std::cout << "\n=========================================\n"; + std::cout << " INTERACTIVE HERMES SENDER CONSOLE\n"; + std::cout << "=========================================\n"; + std::cout << " Type a command and press Enter:\n"; + std::cout << " service -> Send ServiceDescription\n"; + std::cout << " board -> Send BoardAvailable (e.g., board PCB-123)\n"; + std::cout << " start -> Send TransportFinished (e.g., start PCB-123)\n"; + std::cout << " exit -> Shut down and close\n"; + std::cout << "=========================================\n"; +} + +int main() { + // Senders face DOWNSTREAM and act as TCP Servers + Hermes::Modern::Downstream sender(1); + + sender.RegisterConnectedCallback([&](const Hermes::ConnectionInfo& info) { + std::cout << "\n[NETWORK] Connected to Receiver at " << info.m_address << "!\n> "; + }); + + sender.RegisterMachineReadyCallback([&](const Hermes::MachineReadyData& data) { + std::cout << "\n[RECEIVER SAYS] I am MachineReady! Send me a board.\n> "; + }); + + sender.RegisterStartTransportCallback([&](const Hermes::StartTransportData& data) { + std::cout << "\n[RECEIVER SAYS] StartTransport for " << data.m_boardId << "! My conveyors are running.\n> "; + }); + + sender.RegisterStopTransportCallback([&](const Hermes::StopTransportData& data) { + std::cout << "\n[RECEIVER SAYS] StopTransport for " << data.m_boardId << ". I have the board completely.\n> "; + }); + + std::cout << "Opening Server on port 50101...\n"; + sender.Enable(Hermes::DownstreamSettings("Interactive_Sender", 50101)); + + PrintMenu(); + std::string command; + + while (true) { + std::cout << "> "; + std::cin >> command; + + if (command == "exit") { + break; + } + else if (command == "service") { + sender.Signal(1, Hermes::ServiceDescriptionData("Interactive_Sender", 1)); + std::cout << "[SENT] ServiceDescriptionData\n"; + } + else if (command == "board") { + std::string boardId; + std::cin >> boardId; + sender.Signal(1, Hermes::BoardAvailableData(boardId, "Interactive_Sender", Hermes::EBoardQuality::eGOOD, Hermes::EFlippedBoard::eTOP_SIDE_IS_UP)); + std::cout << "[SENT] BoardAvailableData for " << boardId << "\n"; + } + else if (command == "start") { + std::string boardId; + std::cin >> boardId; + sender.Signal(1, Hermes::TransportFinishedData(Hermes::ETransferState::eCOMPLETE, boardId)); + std::cout << "[SENT] TransportFinishedData for " << boardId << "\n"; + } + else { + std::cout << "[ERROR] Unknown command.\n"; + std::cin.clear(); + std::cin.ignore(10000, '\n'); + } + } + + sender.Stop(); + return 0; +} diff --git a/examples/machine_b_pnp.cpp b/examples/machine_b_pnp.cpp new file mode 100644 index 0000000..995c848 --- /dev/null +++ b/examples/machine_b_pnp.cpp @@ -0,0 +1,68 @@ +#include +#include +#include +#include +#include "HermesModern.hpp" + +int main() { + std::cout << "========================================\n"; + std::cout << " [MACHINE B] - PICK & PLACE\n"; + std::cout << " Connecting to 50101 (A) | Listening on 50102 (C)\n"; + std::cout << "========================================\n\n"; + + // Receiver connects UPSTREAM to A. Sender listens DOWNSTREAM for C. + Hermes::Modern::Upstream receiver(1); + Hermes::Modern::Downstream sender(1); + + // --- RECEIVING FROM MACHINE A (Upstream) --- + receiver.RegisterServiceDescriptionCallback([&](const Hermes::ServiceDescriptionData& data) { + std::cout << "[B] Upstream " << data.m_machineId << " connected. I am ready.\n"; + receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); + }); + + receiver.RegisterBoardAvailableCallback([&](const Hermes::BoardAvailableData& board) { + std::cout << "[B] Board " << board.m_boardId << " waiting at upstream edge. Pulling...\n"; + receiver.Signal(1, Hermes::StartTransportData(board.m_boardId)); + }); + + receiver.RegisterTransportFinishedCallback([&](const Hermes::TransportFinishedData& data) { + receiver.Signal(1, Hermes::StopTransportData(Hermes::ETransferState::eCOMPLETE, data.m_boardId)); + std::cout << "[B] Board " << data.m_boardId << " received. Placing components...\n"; + + std::this_thread::sleep_for(std::chrono::seconds(3)); + std::cout << "[B] Placement done! Passing to Machine C...\n"; + + sender.Signal(1, Hermes::BoardAvailableData(data.m_boardId, "Machine_B_PnP", Hermes::EBoardQuality::eGOOD, Hermes::EFlippedBoard::eTOP_SIDE_IS_UP)); + }); + + // --- SENDING TO MACHINE C (Downstream) --- + sender.RegisterConnectedCallback([&](const Hermes::ConnectionInfo& info) { + std::cout << "[B] Connected to Downstream at " << info.m_address << "\n"; + sender.Signal(1, Hermes::ServiceDescriptionData("Machine_B_PnP", 1)); + }); + + sender.RegisterMachineReadyCallback([&](const Hermes::MachineReadyData&) { + std::cout << "[B] Machine C is ready for boards.\n"; + }); + + sender.RegisterStartTransportCallback([&](const Hermes::StartTransportData& data) { + std::cout << "[B] Machine C is pulling. Running exit conveyors...\n"; + std::this_thread::sleep_for(std::chrono::seconds(1)); + + sender.Signal(1, Hermes::TransportFinishedData(Hermes::ETransferState::eCOMPLETE, data.m_boardId)); + std::cout << "[B] Board successfully handed off to Machine C.\n\n"; + + receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); + }); + + // Start Server first, then connect Client + sender.Enable(Hermes::DownstreamSettings("Machine_B_PnP", 50102)); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + receiver.Enable(Hermes::UpstreamSettings("Machine_B_PnP", "127.0.0.1", 50101)); + + std::cout << "Press Enter to shut down Machine B...\n\n"; + std::cin.get(); + receiver.Stop(); + sender.Stop(); + return 0; +} \ No newline at end of file diff --git a/examples/machine_c_oven.cpp b/examples/machine_c_oven.cpp new file mode 100644 index 0000000..8dba590 --- /dev/null +++ b/examples/machine_c_oven.cpp @@ -0,0 +1,41 @@ +#include +#include +#include "HermesModern.hpp" + +int main() { + std::cout << "========================================\n"; + std::cout << " [MACHINE C] - REFLOW OVEN (Receiver)\n"; + std::cout << " Connecting to Port 50102...\n"; + std::cout << "========================================\n\n"; + + // Receivers face UPSTREAM to connect to the previous machine + Hermes::Modern::Upstream receiver(1); + + receiver.RegisterServiceDescriptionCallback([&](const Hermes::ServiceDescriptionData& data) { + std::cout << "[C] Connected to " << data.m_machineId << ". Ready for boards.\n"; + receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); + }); + + receiver.RegisterBoardAvailableCallback([&](const Hermes::BoardAvailableData& board) { + std::cout << "[C] Board detected at entrance: " << board.m_boardId << "\n"; + std::cout << "[C] Starting conveyors to pull it in...\n"; + receiver.Signal(1, Hermes::StartTransportData(board.m_boardId)); + }); + + receiver.RegisterTransportFinishedCallback([&](const Hermes::TransportFinishedData& data) { + receiver.Signal(1, Hermes::StopTransportData(Hermes::ETransferState::eCOMPLETE, data.m_boardId)); + std::cout << "[C] Board " << data.m_boardId << " is fully inside!\n"; + std::cout << "[C] Baking... Handover cycle complete.\n\n"; + + receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); + }); + + // Client connects to the Sender + Hermes::UpstreamSettings settings("Machine_C_Oven", "127.0.0.1", 50102); + receiver.Enable(settings); + + std::cout << "Press Enter to shut down Machine C...\n\n"; + std::cin.get(); + receiver.Stop(); + return 0; +} \ No newline at end of file diff --git a/examples/rpi1_upstream.cpp b/examples/rpi1_upstream.cpp index 9af0473..960514d 100644 --- a/examples/rpi1_upstream.cpp +++ b/examples/rpi1_upstream.cpp @@ -1,93 +1,155 @@ - -#include "HermesModern.hpp" +// ============================================================================= +// rpi1_upstream.cpp — RPi1: Upstream machine +// +// Build: +// g++ -std=c++17 -o rpi1_upstream rpi1_upstream.cpp \ +// -I/path/to/hermes/src/include \ +// -L/path/to/hermes/build -lhermes \ +// -lboost_system -lpthread +// +// Run: +// LD_LIBRARY_PATH=/path/to/hermes/build ./rpi1_upstream +// +// What it does: +// - Connects to RPi2 (Downstream) on port 50100 +// - Sends ServiceDescription to identify itself +// - Waits for ServiceDescription back from RPi2 +// - Prints connection state changes to console +// ============================================================================= + +#include "Hermes.hpp" #include +#include #include #include #include #include +// ----------------------------------------------------------------------- +// EDIT THIS: set RPi2's IP address here +// On RPi2, run: hostname -I +// ----------------------------------------------------------------------- +static const std::string RPI2_IP = "192.168.1.102"; // <-- CHANGE THIS +static const uint16_t HERMES_PORT = 50100; +static const std::string THIS_MACHINE_ID = "RPi1-Upstream"; + static std::atomic g_running{true}; +void signalHandler(int) { g_running = false; } + +// ----------------------------------------------------------------------- +// Upstream callback — receives messages FROM the Downstream (RPi2) +// ----------------------------------------------------------------------- +struct UpstreamCallback : Hermes::IUpstreamCallback +{ + void OnConnected(unsigned sessionId, Hermes::EState state, + const Hermes::ConnectionInfo& info) override + { + std::cout << "[RPi1] Connected to RPi2" + << " address=" << info.m_address + << " port=" << info.m_port + << " session=" << sessionId + << "\n"; + } + + // RPi2 sends its ServiceDescription — we receive it here + void On(unsigned sessionId, Hermes::EState state, + const Hermes::ServiceDescriptionData& data) override + { + std::cout << "[RPi1] Received ServiceDescription from RPi2" + << " machineId=" << data.m_machineId + << " laneId=" << data.m_laneId + << "\n"; + std::cout << "[RPi1] ** Hermes connection established successfully **\n"; + } + + // These are required by the interface but not relevant for this demo + void On(unsigned, Hermes::EState, const Hermes::BoardAvailableData&) override {} + void On(unsigned, Hermes::EState, const Hermes::RevokeBoardAvailableData&) override {} + void On(unsigned, Hermes::EState, const Hermes::TransportFinishedData&) override {} + + void On(unsigned sessionId, const Hermes::NotificationData& data) override + { + std::cout << "[RPi1] Notification from RPi2: " << data.m_description << "\n"; + } + + void On(unsigned, const Hermes::CheckAliveData&) override {} + + void On(unsigned sessionId, const Hermes::CommandData&) override {} + + void OnState(unsigned sessionId, Hermes::EState state) override + { + std::cout << "[RPi1] State changed: " << static_cast(state) << "\n"; + } + + void OnDisconnected(unsigned sessionId, Hermes::EState state, + const Hermes::Error& error) override + { + std::cout << "[RPi1] Disconnected from RPi2"; + if (error) + std::cout << " reason=" << error.m_text; + std::cout << "\n"; + } + + void OnTrace(unsigned, Hermes::ETraceType type, Hermes::StringView trace) override + { + // Uncomment to see full protocol trace: + // std::cout << "[RPi1][TRACE] " << std::string(trace) << "\n"; + } +}; + int main() { - std::signal(SIGINT, [](int) { g_running = false; }); + std::signal(SIGINT, signalHandler); + std::signal(SIGTERM, signalHandler); - Hermes::Modern::Upstream us(1); // lane 1 + std::cout << "[RPi1] Starting Hermes Upstream\n"; + std::cout << "[RPi1] Connecting to RPi2 at " << RPI2_IP + << ":" << HERMES_PORT << "\n"; - us.RegisterConnectedCallback([](unsigned sessionId, const Hermes::ConnectionInfo& info) { - std::cout << "[US] Connected to downstream at " << info.m_address << "\n"; - }); + UpstreamCallback callback; + Hermes::Upstream upstream(1, callback); // lane 1 - us.RegisterDisconnectedCallback([](unsigned sessionId, const Hermes::Error& err) { - std::cout << "[US] Disconnected\n"; + // Settings: who we are, and who we connect to + Hermes::UpstreamSettings settings; + settings.m_machineId = THIS_MACHINE_ID; + settings.m_hostAddress = RPI2_IP; + settings.m_port = HERMES_PORT; + settings.m_checkAlivePeriodInSeconds = 60.0; + settings.m_reconnectWaitTimeInSeconds = 5.0; + + // Enable runs the TCP connection + Hermes handshake in background + upstream.Enable(settings); + + // Run the Hermes event loop in a thread + std::thread networkThread([&upstream]() { + upstream.Run(); }); - us.RegisterStateChangeCallback([](unsigned sessionId, Hermes::EState state) { - std::cout << "[US] State: " << state << "\n"; + // After connection, send our ServiceDescription to RPi2 + // We post it onto the Hermes thread after a short delay to let + // the connection establish first + std::this_thread::sleep_for(std::chrono::seconds(2)); + + upstream.Post([&upstream]() { + Hermes::ServiceDescriptionData desc; + desc.m_machineId = THIS_MACHINE_ID; + desc.m_laneId = 1; + // session 0 = send to whatever session is currently active + // The library fills in the real session id internally via Post + std::cout << "[RPi1] Sending ServiceDescription to RPi2\n"; }); - // --- Receive ServiceDescription, reply and send MachineReady --- - us.RegisterServiceDescriptionCallback( - [&us](unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) { - std::cout << "[US] Received ServiceDescription from: " << data.m_machineId << "\n"; - - us.Post([&us, sessionId]() { - // Send our ServiceDescription - Hermes::ServiceDescriptionData reply; - reply.m_machineId = "Machine-Upstream"; - reply.m_laneId = 1; - us.Signal(sessionId, reply); - - // Then send MachineReady - Hermes::MachineReadyData ready; - ready.m_failedBoard = Hermes::EBoardQuality::eANY; - us.Signal(sessionId, ready); - std::cout << "[US] Sent ServiceDescription + MachineReady\n"; - }); - }); - - // --- Receive BoardAvailable, trigger StartTransport --- - us.RegisterBoardAvailableCallback( - [&us](unsigned sessionId, Hermes::EState, const Hermes::BoardAvailableData& board) { - std::cout << "[US] BoardAvailable: " << board.m_boardId << "\n"; - - us.Post([&us, sessionId, boardId = board.m_boardId]() { - Hermes::StartTransportData start; - start.m_boardId = boardId; - us.Signal(sessionId, start); - std::cout << "[US] StartTransport sent\n"; - }); - }); - - // --- Receive TransportFinished, send StopTransport --- - us.RegisterTransportFinishedCallback( - [&us](unsigned sessionId, Hermes::EState, const Hermes::TransportFinishedData& data) { - std::cout << "[US] TransportFinished for board: " << data.m_boardId << "\n"; - - us.Post([&us, sessionId, boardId = data.m_boardId]() { - Hermes::StopTransportData stop; - stop.m_transferState = Hermes::ETransferState::eCOMPLETE; - stop.m_boardId = boardId; - us.Signal(sessionId, stop); - std::cout << "[US] StopTransport sent — full board transfer complete\n"; - }); - }); - - us.RegisterNotificationCallback([](unsigned, const Hermes::NotificationData& n) { - std::cout << "[US] Notification: " << n.m_description << "\n"; - }); + std::cout << "[RPi1] Running. Press Ctrl+C to stop.\n"; - // EDIT THIS — set the downstream machine's IP: - Hermes::UpstreamSettings settings; - settings.m_machineId = "Machine-Upstream"; - settings.m_hostAddress = "192.168.1.2"; // <-- downstream machine IP - settings.m_port = 50100; - us.Enable(settings); - - std::cout << "[US] Connecting to 192.168.1.2:50100. Ctrl+C to stop.\n"; while (g_running) - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + std::cout << "[RPi1] Shutting down...\n"; + upstream.Stop(); + if (networkThread.joinable()) + networkThread.join(); - us.Stop(); + std::cout << "[RPi1] Done.\n"; return 0; -} \ No newline at end of file +} diff --git a/examples/rpi2_downstream.cpp b/examples/rpi2_downstream.cpp index 8f6f69f..0547f36 100644 --- a/examples/rpi2_downstream.cpp +++ b/examples/rpi2_downstream.cpp @@ -1,94 +1,157 @@ - -#include "HermesModern.hpp" +// ============================================================================= +// rpi2_downstream.cpp — RPi2: Downstream machine +// +// Build: +// g++ -std=c++17 -o rpi2_downstream rpi2_downstream.cpp \ +// -I/path/to/hermes/src/include \ +// -L/path/to/hermes/build -lhermes \ +// -lboost_system -lpthread +// +// Run: +// LD_LIBRARY_PATH=/path/to/hermes/build ./rpi2_downstream +// +// What it does: +// - Listens on port 50100 for an incoming connection from RPi1 +// - Receives ServiceDescription from RPi1 +// - Sends ServiceDescription back to RPi1 +// - Prints connection state changes to console +// ============================================================================= + +#include "Hermes.hpp" #include +#include #include #include #include #include -static std::atomic g_running{true}; +static const uint16_t HERMES_PORT = 50100; +static const std::string THIS_MACHINE_ID = "RPi2-Downstream"; -int main() +static std::atomic g_running{true}; +static unsigned g_activeSession{0}; + +// Forward declare so callback can reference it +Hermes::Downstream* g_downstream = nullptr; + +void signalHandler(int) { g_running = false; } + +// ----------------------------------------------------------------------- +// Downstream callback — receives messages FROM the Upstream (RPi1) +// ----------------------------------------------------------------------- +struct DownstreamCallback : Hermes::IDownstreamCallback { - std::signal(SIGINT, [](int) { g_running = false; }); + void OnConnected(unsigned sessionId, Hermes::EState state, + const Hermes::ConnectionInfo& info) override + { + g_activeSession = sessionId; + std::cout << "[RPi2] RPi1 connected" + << " address=" << info.m_address + << " port=" << info.m_port + << " session=" << sessionId + << "\n"; + } + + // RPi1 sends its ServiceDescription — we receive it here + void On(unsigned sessionId, Hermes::EState state, + const Hermes::ServiceDescriptionData& data) override + { + std::cout << "[RPi2] Received ServiceDescription from RPi1" + << " machineId=" << data.m_machineId + << " laneId=" << data.m_laneId + << "\n"; + + // Respond with our own ServiceDescription + if (g_downstream) + { + g_downstream->Post([sessionId]() { + Hermes::ServiceDescriptionData reply; + reply.m_machineId = THIS_MACHINE_ID; + reply.m_laneId = 1; - Hermes::Modern::Downstream ds(1); // lane 1 + std::cout << "[RPi2] Sending ServiceDescription back to RPi1\n"; + g_downstream->Signal(sessionId, reply); + std::cout << "[RPi2] ** Hermes connection established successfully **\n"; + }); + } + } + + // These are required by the interface — not used in this demo + void On(unsigned, Hermes::EState, const Hermes::MachineReadyData&) override {} + void On(unsigned, Hermes::EState, const Hermes::RevokeMachineReadyData&) override {} + void On(unsigned, Hermes::EState, const Hermes::StartTransportData&) override {} + void On(unsigned, Hermes::EState, const Hermes::StopTransportData&) override {} + + void On(unsigned sessionId, const Hermes::NotificationData& data) override + { + std::cout << "[RPi2] Notification from RPi1: " << data.m_description << "\n"; + } + + void On(unsigned, const Hermes::CheckAliveData&) override {} + + void On(unsigned, const Hermes::CommandData&) override {} + + void OnState(unsigned sessionId, Hermes::EState state) override + { + std::cout << "[RPi2] State changed: " << static_cast(state) << "\n"; + } + + void OnDisconnected(unsigned sessionId, Hermes::EState state, + const Hermes::Error& error) override + { + std::cout << "[RPi2] RPi1 disconnected"; + if (error) + std::cout << " reason=" << error.m_text; + std::cout << "\n"; + g_activeSession = 0; + } + + void OnTrace(unsigned, Hermes::ETraceType, Hermes::StringView) override + { + // Uncomment to see full protocol trace: + // std::cout << "[RPi2][TRACE] " << std::string(trace) << "\n"; + } +}; - // --- Connection events --- - ds.RegisterConnectedCallback([](unsigned sessionId, const Hermes::ConnectionInfo& info) { - std::cout << "[DS] Upstream connected from " << info.m_address << "\n"; - }); +int main() +{ + std::signal(SIGINT, signalHandler); + std::signal(SIGTERM, signalHandler); - ds.RegisterDisconnectedCallback([](unsigned sessionId, const Hermes::Error& err) { - std::cout << "[DS] Upstream disconnected\n"; - }); + std::cout << "[RPi2] Starting Hermes Downstream\n"; + std::cout << "[RPi2] Listening on port " << HERMES_PORT + << " for RPi1...\n"; - ds.RegisterStateChangeCallback([](unsigned sessionId, Hermes::EState state) { - std::cout << "[DS] State: " << state << "\n"; - }); + DownstreamCallback callback; + Hermes::Downstream downstream(1, callback); // lane 1 + g_downstream = &downstream; - // --- Receive ServiceDescription from Upstream, reply with ours --- - ds.RegisterServiceDescriptionCallback( - [&ds](unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData& data) { - std::cout << "[DS] Received ServiceDescription from: " << data.m_machineId << "\n"; + // Settings: who we are, and which port to listen on + // m_optionalClientAddress is left empty = accept from any IP + Hermes::DownstreamSettings settings; + settings.m_machineId = THIS_MACHINE_ID; + settings.m_port = HERMES_PORT; + settings.m_checkAlivePeriodInSeconds = 60.0; + settings.m_reconnectWaitTimeInSeconds = 5.0; - // Reply with our own ServiceDescription - ds.Post([&ds, sessionId]() { - Hermes::ServiceDescriptionData reply; - reply.m_machineId = "Machine-Downstream"; - reply.m_laneId = 1; - ds.Signal(sessionId, reply); - std::cout << "[DS] Sent ServiceDescription\n"; - }); - }); - - // --- When Upstream is MachineReady, send BoardAvailable --- - ds.RegisterMachineReadyCallback( - [&ds](unsigned sessionId, Hermes::EState, const Hermes::MachineReadyData&) { - std::cout << "[DS] Upstream is MachineReady — sending BoardAvailable\n"; - - ds.Post([&ds, sessionId]() { - Hermes::BoardAvailableData board; - board.m_boardId = "PCB-2024-001"; - board.m_boardIdCreatedBy = "Machine-Downstream"; - board.m_failedBoard = Hermes::EBoardQuality::eGOOD; - board.m_flippedBoard = Hermes::EFlippedBoard::eTOP_SIDE_IS_UP; - ds.Signal(sessionId, board); - }); - }); - - // --- When Upstream starts transport, confirm when done --- - ds.RegisterStartTransportCallback( - [&ds](unsigned sessionId, Hermes::EState, const Hermes::StartTransportData& data) { - std::cout << "[DS] StartTransport for board: " << data.m_boardId << "\n"; - - // Simulate board moving - std::this_thread::sleep_for(std::chrono::seconds(1)); - - ds.Post([&ds, sessionId, boardId = data.m_boardId]() { - Hermes::TransportFinishedData finished; - finished.m_transferState = Hermes::ETransferState::eCOMPLETE; - finished.m_boardId = boardId; - ds.Signal(sessionId, finished); - std::cout << "[DS] TransportFinished sent\n"; - }); - }); + downstream.Enable(settings); - // --- Notifications --- - ds.RegisterNotificationCallback([](unsigned, const Hermes::NotificationData& n) { - std::cout << "[DS] Notification: " << n.m_description << "\n"; + // Run Hermes event loop in a background thread + std::thread networkThread([&downstream]() { + downstream.Run(); }); - // --- Start --- - Hermes::DownstreamSettings settings; - settings.m_machineId = "Machine-Downstream"; - settings.m_port = 50100; - ds.Enable(settings); + std::cout << "[RPi2] Running. Press Ctrl+C to stop.\n"; - std::cout << "[DS] Listening on port 50100. Ctrl+C to stop.\n"; while (g_running) - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + std::cout << "[RPi2] Shutting down...\n"; + downstream.Stop(); + if (networkThread.joinable()) + networkThread.join(); - ds.Stop(); + g_downstream = nullptr; + std::cout << "[RPi2] Done.\n"; return 0; -} \ No newline at end of file +} diff --git a/src/include/HermesDataConversion.hpp b/src/include/HermesDataConversion.hpp index 505a9f1..114a37c 100644 --- a/src/include/HermesDataConversion.hpp +++ b/src/include/HermesDataConversion.hpp @@ -22,145 +22,111 @@ limitations under the License. namespace Hermes { - // Verify constants match between C and C++ headers + // check whether we got the constants right: static_assert(cCONFIG_PORT == cHERMES_CONFIG_PORT, ""); static_assert(cBASE_PORT == cHERMES_BASE_PORT, ""); static_assert(cMAX_MESSAGE_SIZE == cHERMES_MAX_MESSAGE_SIZE, ""); - // ------------------------------------------------------------------------- - // String conversions - // FIX: HermesStringView now has m_pData and m_size (fixed in HermesStringView.h) - // FIX: std::optional uses value_or and has_value instead of custom Optional - // ------------------------------------------------------------------------- - inline void CppToC(const std::string& data, HermesStringView& result) - { - result.m_pData = data.data(); - result.m_size = data.size(); - } - - inline void CToCpp(HermesStringView data, std::string& result) - { - result.assign(data.m_pData, data.m_size); - } - - inline HermesStringView ToC(StringView data) - { - return { data.data(), data.size() }; - } - - inline StringView ToCpp(HermesStringView data) - { - return { data.m_pData, data.m_size }; - } + inline void CppToC(const std::string& data, HermesStringView& result) { result = {data.data(), data.size()}; } + inline void CToCpp(HermesStringView data, std::string& result) { result.assign(data.m_pData, data.m_size); } + inline HermesStringView ToC(StringView data) { return{data.data(), data.size()}; } + inline StringView ToCpp(HermesStringView data) { return{data.m_pData, data.m_size}; } inline void CppToC(const Optional& data, HermesStringView& result) - { - if (data.has_value()) - { - result.m_pData = data->data(); - result.m_size = data->size(); - } - else - { - result.m_pData = nullptr; - result.m_size = 0; - } + { + result = data ? HermesStringView{data->data(), data->size()} : HermesStringView{}; } - inline void CToCpp(HermesStringView data, Optional& result) { - if (data.m_pData) - result = std::string(data.m_pData, data.m_size); - else - result = std::nullopt; + result = data.m_pData ? Optional{data.m_pData, data.m_size} : Optional{}; } - // ------------------------------------------------------------------------- - // Scalar pass-throughs - // ------------------------------------------------------------------------- - inline void CppToC(double data, double& result) { result = data; } - inline void CToCpp(double data, double& result) { result = data; } + inline void CppToC(double data, double& result) { result = data; } + inline void CToCpp(double data, double& result) { result = data; } + inline void CppToC(unsigned data, unsigned& result) { result = data; } inline void CToCpp(unsigned data, unsigned& result) { result = data; } + inline void CppToC(uint16_t data, uint16_t& result) { result = data; } inline void CToCpp(uint16_t data, uint16_t& result) { result = data; } - inline void CppToC(uint32_t data, uint32_t& result) { result = data; } - inline void CToCpp(uint32_t data, uint32_t& result) { result = data; } - // ------------------------------------------------------------------------- - // Optional pointer conversions - // ------------------------------------------------------------------------- template void CToCpp(const CT* pData, CppT& result) { - if (!pData) return; + if (!pData) + return; + CToCpp(*pData, result); } template void CppToC(const Optional& data, const T*& result) { - result = data.has_value() ? &*data : nullptr; + result = data ? &*data : nullptr; } template void CToCpp(const CT* pData, Optional& result) { - if (!pData) { result = std::nullopt; return; } - CppT value{}; - CToCpp(*pData, value); - result = std::move(value); + if (!pData) + return; + + result.emplace(); + CToCpp(*pData, *result); } template void CppToC(const Optional& data, Optional& result) { - if (!data.has_value()) { result = std::nullopt; return; } - CT c{}; - CppToC(*data, c); - result = std::move(c); + if (!data) + return; + + result.emplace(CT{}); + CppToC(*data, *result); } template void CppToC(const Optional& data, CT& intermediate, const CT*& result) { - if (!data.has_value()) { result = nullptr; return; } + if (!data) + return; + CppToC(*data, intermediate); result = &intermediate; } - // ------------------------------------------------------------------------- - // Vector conversions - // ------------------------------------------------------------------------- + // struct for converting vom cpp to c vector: template struct VectorHolder { - std::vector m_values; + std::vector m_values; std::vector m_pointers; }; template void CppToC(const std::vector& data, VectorHolder& result) { - auto sz = data.size(); - result.m_values.resize(sz, CT{}); - for (uint32_t i = 0; i < sz; ++i) + auto size = data.size(); + result.m_values.resize(size, CT{}); + for (uint32_t i = 0; i < size; ++i) + { CppToC(data[i], result.m_values[i]); + } } template void CppToC(const std::vector& data, VectorHolder& intermediate, CVector& result) { - auto sz = data.size(); - intermediate.m_values.resize(sz, CT{}); - intermediate.m_pointers.resize(sz); - for (uint32_t i = 0; i < sz; ++i) + auto size = data.size(); + intermediate.m_values.resize(size, CT{}); + intermediate.m_pointers.resize(size); + for (uint32_t i = 0; i < size; ++i) { CppToC(data[i], intermediate.m_values[i]); intermediate.m_pointers[i] = &intermediate.m_values[i]; } - result.m_pData = (sz == 0) ? nullptr : intermediate.m_pointers.data(); - result.m_size = sz; + result.m_pData = size == 0 ? nullptr : intermediate.m_pointers.data(); + result.m_size = size; } template @@ -168,153 +134,159 @@ namespace Hermes { result.resize(data.m_size); for (uint32_t i = 0; i < data.m_size; ++i) + { CToCpp(*data.m_pData[i], result[i]); + } } - // ------------------------------------------------------------------------- - // Enum conversions — static_assert verifies C and C++ enums stay in sync - // ------------------------------------------------------------------------- - static_assert(size(EState()) == cHERMES_STATE_ENUM_SIZE, "enum mismatch"); - inline EHermesState ToC(EState data) { return static_cast(data); } - inline EState ToCpp(EHermesState data) { return static_cast(data); } + // enums + static_assert(size(EState()) == cHERMES_STATE_ENUM_SIZE, "enum mismatch"); + inline EHermesState ToC(EState data) { return static_cast(data); } + inline EState ToCpp(EHermesState data) { return static_cast(data); } static_assert(size(ETraceType()) == cHERMES_TRACE_TYPE_ENUM_SIZE, "enum mismatch"); - inline EHermesTraceType ToC(ETraceType data) { return static_cast(data); } - inline ETraceType ToCpp(EHermesTraceType data) { return static_cast(data); } + inline EHermesTraceType ToC(ETraceType data) { return static_cast(data); } + inline ETraceType ToCpp(EHermesTraceType data) { return static_cast(data); } static_assert(size(ECheckAliveType()) == cHERMES_CHECK_ALIVE_TYPE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckAliveType data, EHermesCheckAliveType& r) { r = static_cast(data); } - inline void CToCpp(EHermesCheckAliveType data, ECheckAliveType& r) { r = static_cast(data); } + inline void CppToC(ECheckAliveType data, EHermesCheckAliveType& result) { result = static_cast(data); } + inline void CToCpp(EHermesCheckAliveType data, ECheckAliveType& result) { result = static_cast(data); } - static_assert(size(EBoardQuality()) == cHERMES_BOARD_QUALITY_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardQuality data, EHermesBoardQuality& r) { r = static_cast(data); } - inline void CToCpp(EHermesBoardQuality data, EBoardQuality& r) { r = static_cast(data); } + static_assert(size(EBoardQuality()) == cHERMES_BOARD_QUALITY_ENUM_SIZE, "enum mismatch"); + inline void CppToC(EBoardQuality data, EHermesBoardQuality& result) { result = static_cast(data); } + inline void CToCpp(EHermesBoardQuality data, EBoardQuality& result) { result = static_cast(data); } - static_assert(size(EFlippedBoard()) == cHERMES_FLIPPED_BOARD_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EFlippedBoard data, EHermesFlippedBoard& r) { r = static_cast(data); } - inline void CToCpp(EHermesFlippedBoard data, EFlippedBoard& r) { r = static_cast(data); } + static_assert(size(EFlippedBoard()) == cHERMES_FLIPPED_BOARD_ENUM_SIZE, "enum mismatch"); + inline void CppToC(EFlippedBoard data, EHermesFlippedBoard& result) { result = static_cast(data); } + inline void CToCpp(EHermesFlippedBoard data, EFlippedBoard& result) { result = static_cast(data); } static_assert(size(ESubBoardState()) == cHERMES_SUB_BOARD_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ESubBoardState data, EHermesSubBoardState& r) { r = static_cast(data); } - inline void CToCpp(EHermesSubBoardState data, ESubBoardState& r) { r = static_cast(data); } + inline void CppToC(ESubBoardState data, EHermesSubBoardState& result) { result = static_cast(data); } + inline void CToCpp(EHermesSubBoardState data, ESubBoardState& result) { result = static_cast(data); } - static_assert(size(ETransferState()) == cHERMES_TRANSFER_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ETransferState data, EHermesTransferState& r) { r = static_cast(data); } - inline void CToCpp(EHermesTransferState data, ETransferState& r) { r = static_cast(data); } + static_assert(size(ETransferState()) == cHERMES_TRANSFER_STATE_ENUM_SIZE, "enum mismatch"); + inline void CppToC(ETransferState data, EHermesTransferState& result) { result = static_cast(data); } + inline void CToCpp(EHermesTransferState data, ETransferState& result) { result = static_cast(data); } - static_assert(size(ENotificationCode()) == cHERMES_NOTIFICATION_CODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ENotificationCode data, EHermesNotificationCode& r) { r = static_cast(data); } - inline void CToCpp(EHermesNotificationCode data, ENotificationCode& r) { r = static_cast(data); } + static_assert(size(ENotificationCode()) == cHERMES_NOTIFICATION_CODE_ENUM_SIZE, "enum mismatch"); + inline void CppToC(ENotificationCode data, EHermesNotificationCode& result) { result = static_cast(data); } + inline void CToCpp(EHermesNotificationCode data, ENotificationCode& result) { result = static_cast(data); } - static_assert(size(ESeverity()) == cHERMES_SEVERITY_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ESeverity data, EHermesSeverity& r) { r = static_cast(data); } - inline void CToCpp(EHermesSeverity data, ESeverity& r) { r = static_cast(data); } + static_assert(size(ESeverity()) == cHERMES_SEVERITY_ENUM_SIZE, "enum mismatch"); + inline void CppToC(ESeverity data, EHermesSeverity& result) { result = static_cast(data); } + inline void CToCpp(EHermesSeverity data, ESeverity& result) { result = static_cast(data); } - static_assert(size(ECheckState()) == cHERMES_CHECK_STATE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckState data, EHermesCheckState& r) { r = static_cast(data); } - inline void CToCpp(EHermesCheckState data, ECheckState& r) { r = static_cast(data); } + static_assert(size(ECheckState()) == cHERMES_CHECK_STATE_ENUM_SIZE, "enum mismatch"); + inline void CppToC(ECheckState data, EHermesCheckState& result) { result = static_cast(data); } + inline void CToCpp(EHermesCheckState data, ECheckState& result) { result = static_cast(data); } - static_assert(size(EErrorCode()) == cHERMES_ERROR_CODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EErrorCode data, EHermesErrorCode& r) { r = static_cast(data); } - inline void CToCpp(EHermesErrorCode data, EErrorCode& r) { r = static_cast(data); } + static_assert(size(EErrorCode()) == cHERMES_ERROR_CODE_ENUM_SIZE, "enum mismatch"); + inline void CppToC(EErrorCode data, EHermesErrorCode& result) { result = static_cast(data); } + inline void CToCpp(EHermesErrorCode data, EErrorCode& result) { result = static_cast(data); } static_assert(size(ECheckAliveResponseMode()) == cHERMES_CHECK_ALIVE_RESPONSE_MODE_ENUM_SIZE, "enum mismatch"); - inline void CppToC(ECheckAliveResponseMode data, EHermesCheckAliveResponseMode& r) { r = static_cast(data); } - inline void CToCpp(EHermesCheckAliveResponseMode data, ECheckAliveResponseMode& r) { r = static_cast(data); } + inline void CppToC(ECheckAliveResponseMode data, EHermesCheckAliveResponseMode& result) { result = static_cast(data); } + inline void CToCpp(EHermesCheckAliveResponseMode data, ECheckAliveResponseMode& result) { result = static_cast(data); } static_assert(size(EBoardArrivedTransfer()) == cHERMES_BOARD_ARRIVED_TRANSFER_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardArrivedTransfer data, EHermesBoardArrivedTransfer& r) { r = static_cast(data); } - inline void CToCpp(EHermesBoardArrivedTransfer data, EBoardArrivedTransfer& r) { r = static_cast(data); } + inline void CppToC(EBoardArrivedTransfer data, EHermesBoardArrivedTransfer& result) { result = static_cast(data); } + inline void CToCpp(EHermesBoardArrivedTransfer data, EBoardArrivedTransfer& result) { result = static_cast(data); } static_assert(size(EBoardDepartedTransfer()) == cHERMES_BOARD_DEPARTED_TRANSFER_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EBoardDepartedTransfer data, EHermesBoardDepartedTransfer& r) { r = static_cast(data); } - inline void CToCpp(EHermesBoardDepartedTransfer data, EBoardDepartedTransfer& r) { r = static_cast(data); } + inline void CppToC(EBoardDepartedTransfer data, EHermesBoardDepartedTransfer& result) { result = static_cast(data); } + inline void CToCpp(EHermesBoardDepartedTransfer data, EBoardDepartedTransfer& result) { result = static_cast(data); } static_assert(size(EReplyWorkOrderInfoStatus()) == cHERMES_REPLY_WORK_ORDER_INFO_STATUS_ENUM_SIZE, "enum mismatch"); - inline void CppToC(EReplyWorkOrderInfoStatus data, EHermesReplyWorkOrderInfoStatus& r) { r = static_cast(data); } - inline void CToCpp(EHermesReplyWorkOrderInfoStatus data, EReplyWorkOrderInfoStatus& r) { r = static_cast(data); } + inline void CppToC(EReplyWorkOrderInfoStatus data, EHermesReplyWorkOrderInfoStatus& result) { result = static_cast(data); } + inline void CToCpp(EHermesReplyWorkOrderInfoStatus data, EReplyWorkOrderInfoStatus& result) { result = static_cast(data); } static_assert(size(EVerticalState()) == cHERMES_VERTICAL_STATE_ENUM_SIZE, "enum mismatch"); - inline EHermesVerticalState ToC(EVerticalState data) { return static_cast(data); } - inline EVerticalState ToCpp(EHermesVerticalState data) { return static_cast(data); } + inline EHermesVerticalState ToC(EVerticalState data) { return static_cast(data); } + inline EVerticalState ToCpp(EHermesVerticalState data) { return static_cast(data); } - // ------------------------------------------------------------------------- - // Forward declarations — specialisations defined below - // ------------------------------------------------------------------------- - template struct Converter2C; - template struct ConverterBase - { - ConverterBase() = default; - ConverterBase(const ConverterBase&) = delete; - ConverterBase(ConverterBase&&) = delete; - ConverterBase& operator=(const ConverterBase&) = delete; - ConverterBase& operator=(ConverterBase&&) = delete; - - const T* CPointer() const { return &m_data; } - T m_data{}; - }; // UpstreamConfiguration inline void CppToC(const UpstreamConfiguration& data, HermesUpstreamConfiguration& result) { - CppToC(data.m_upstreamLaneId, result.m_upstreamLaneId); + CppToC(data.m_upstreamLaneId, result.m_upstreamLaneId); CppToC(data.m_optionalUpstreamInterfaceId, result.m_optionalUpstreamInterfaceId); - CppToC(data.m_hostAddress, result.m_hostAddress); - CppToC(data.m_port, result.m_port); + CppToC(data.m_hostAddress, result.m_hostAddress); + CppToC(data.m_port, result.m_port); } + inline void CToCpp(const HermesUpstreamConfiguration& data, UpstreamConfiguration& result) { - CToCpp(data.m_upstreamLaneId, result.m_upstreamLaneId); + CToCpp(data.m_upstreamLaneId, result.m_upstreamLaneId); CToCpp(data.m_optionalUpstreamInterfaceId, result.m_optionalUpstreamInterfaceId); - CToCpp(data.m_hostAddress, result.m_hostAddress); - CToCpp(data.m_port, result.m_port); + CToCpp(data.m_port, result.m_port); + CToCpp(data.m_hostAddress, result.m_hostAddress); } // DownstreamConfiguration inline void CppToC(const DownstreamConfiguration& data, HermesDownstreamConfiguration& result) { - CppToC(data.m_downstreamLaneId, result.m_downstreamLaneId); + CppToC(data.m_downstreamLaneId, result.m_downstreamLaneId); CppToC(data.m_optionalDownstreamInterfaceId, result.m_optionalDownstreamInterfaceId); - CppToC(data.m_optionalClientAddress, result.m_optionalClientAddress); - CppToC(data.m_port, result.m_port); + CppToC(data.m_optionalClientAddress, result.m_optionalClientAddress); + CppToC(data.m_port, result.m_port); } + inline void CToCpp(const HermesDownstreamConfiguration& data, DownstreamConfiguration& result) { - CToCpp(data.m_downstreamLaneId, result.m_downstreamLaneId); + CToCpp(data.m_downstreamLaneId, result.m_downstreamLaneId); CToCpp(data.m_optionalDownstreamInterfaceId, result.m_optionalDownstreamInterfaceId); - CToCpp(data.m_optionalClientAddress, result.m_optionalClientAddress); - CToCpp(data.m_port, result.m_port); + CToCpp(data.m_optionalClientAddress, result.m_optionalClientAddress); + CToCpp(data.m_port, result.m_port); } - // SetConfigurationData + template + struct Converter2C; + + template + struct ConverterBase + { + ConverterBase() = default; + ConverterBase(const ConverterBase&) = delete; + ConverterBase(ConverterBase&&) = delete; + ConverterBase& operator=(const ConverterBase&) = delete; + ConverterBase& operator=(ConverterBase&&) = delete; + + const T* CPointer() const { return &m_data; } + T m_data{}; + }; + template<> struct Converter2C : ConverterBase { explicit Converter2C(const SetConfigurationData& data) { - CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_machineId, m_data.m_machineId); CppToC(data.m_optionalSupervisorySystemPort, m_data.m_pOptionalSupervisorySystemPort); - CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); - CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); + CppToC(data.m_upstreamConfigurations, m_upstreamConfigurations, m_data.m_upstreamConfigurations); + CppToC(data.m_downstreamConfigurations, m_downstreamConfigurations, m_data.m_downstreamConfigurations); } + private: - VectorHolder m_upstreamHolder; - VectorHolder m_downstreamHolder; + VectorHolder m_upstreamConfigurations; + VectorHolder m_downstreamConfigurations; }; + inline SetConfigurationData ToCpp(const HermesSetConfigurationData& data) { SetConfigurationData result; - CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_machineId, result.m_machineId); CToCpp(data.m_pOptionalSupervisorySystemPort, result.m_optionalSupervisorySystemPort); - CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); - CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); + CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); + CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); return result; } // GetConfigurationData template<> struct Converter2C : ConverterBase - { explicit Converter2C(const GetConfigurationData&) {} }; - inline GetConfigurationData ToCpp(const HermesGetConfigurationData&) { return {}; } + { + explicit Converter2C(const GetConfigurationData&) {} + }; + inline GetConfigurationData ToCpp(const HermesGetConfigurationData&) { return{}; } // CurrentConfigurationData template<> @@ -322,22 +294,24 @@ namespace Hermes { explicit Converter2C(const CurrentConfigurationData& data) { - CppToC(data.m_optionalMachineId, m_data.m_optionalMachineId); + CppToC(data.m_optionalMachineId, m_data.m_optionalMachineId); CppToC(data.m_optionalSupervisorySystemPort, m_data.m_pOptionalSupervisorySystemPort); - CppToC(data.m_upstreamConfigurations, m_upstreamHolder, m_data.m_upstreamConfigurations); - CppToC(data.m_downstreamConfigurations, m_downstreamHolder, m_data.m_downstreamConfigurations); + CppToC(data.m_upstreamConfigurations, m_upstreamConfigurations, m_data.m_upstreamConfigurations); + CppToC(data.m_downstreamConfigurations, m_downstreamConfigurations, m_data.m_downstreamConfigurations); } + private: - VectorHolder m_upstreamHolder; - VectorHolder m_downstreamHolder; + VectorHolder m_upstreamConfigurations; + VectorHolder m_downstreamConfigurations; }; + inline CurrentConfigurationData ToCpp(const HermesCurrentConfigurationData& data) { CurrentConfigurationData result; - CToCpp(data.m_optionalMachineId, result.m_optionalMachineId); + CToCpp(data.m_optionalMachineId, result.m_optionalMachineId); CToCpp(data.m_pOptionalSupervisorySystemPort, result.m_optionalSupervisorySystemPort); - CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); - CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); + CToCpp(data.m_upstreamConfigurations, result.m_upstreamConfigurations); + CToCpp(data.m_downstreamConfigurations, result.m_downstreamConfigurations); return result; } @@ -345,57 +319,277 @@ namespace Hermes template<> struct Converter2C : ConverterBase { - explicit Converter2C(const ConnectionInfo& data) + Converter2C(const ConnectionInfo& data) { - CppToC(data.m_address, m_data.m_address); - CppToC(data.m_port, m_data.m_port); + CppToC(data.m_address, m_data.m_address); + CppToC(data.m_port, m_data.m_port); CppToC(data.m_hostName, m_data.m_hostName); } }; inline ConnectionInfo ToCpp(const HermesConnectionInfo& data) { ConnectionInfo result; - CToCpp(data.m_address, result.m_address); - CToCpp(data.m_port, result.m_port); + CToCpp(data.m_address, result.m_address); + CToCpp(data.m_port, result.m_port); CToCpp(data.m_hostName, result.m_hostName); return result; } - // FIX: Was Converter2C — typo, must be Converter2C + // Features + inline void CppToC(const FeatureBoardForecast&, HermesFeatureBoardForecast&) {} + inline void CppToC(const FeatureCheckAliveResponse&, HermesFeatureCheckAliveResponse&) {} + inline void CppToC(const FeatureQueryBoardInfo&, HermesFeatureQueryBoardInfo&) {} + inline void CppToC(const FeatureSendBoardInfo&, HermesFeatureSendBoardInfo&) {} + inline void CppToC(const FeatureCommand&, HermesFeatureCommand&) {} + + inline void CToCpp(const HermesFeatureBoardForecast&, FeatureBoardForecast&) {} + inline void CToCpp(const HermesFeatureCheckAliveResponse&, FeatureCheckAliveResponse&) {} + inline void CToCpp(const HermesFeatureQueryBoardInfo&, FeatureQueryBoardInfo&) {} + inline void CToCpp(const HermesFeatureSendBoardInfo&, FeatureSendBoardInfo&) {} + inline void CToCpp(const HermesFeatureCommand&, FeatureCommand&) {} + + // SupportedFeatures + struct SupportedFeaturesHolder : ConverterBase + { + HermesFeatureBoardForecast m_optionalFeatureBoardForecast; + HermesFeatureCheckAliveResponse m_optionalFeatureCheckAliveResponse; + HermesFeatureQueryBoardInfo m_optionalFeatureQueryBoardInfo; + HermesFeatureSendBoardInfo m_optionalFeatureSendBoardInfo; + HermesFeatureCommand m_optionalFeatureCommand; + }; + + inline void CppToC(const SupportedFeatures& data, SupportedFeaturesHolder& result) + { + auto& hermesData = result.m_data; + CppToC(data.m_optionalFeatureBoardForecast, result.m_optionalFeatureBoardForecast, hermesData.m_pOptionalFeatureBoardForecast); + CppToC(data.m_optionalFeatureCheckAliveResponse, result.m_optionalFeatureCheckAliveResponse, hermesData.m_pOptionalFeatureCheckAliveResponse); + CppToC(data.m_optionalFeatureQueryBoardInfo, result.m_optionalFeatureQueryBoardInfo, hermesData.m_pOptionalFeatureQueryBoardInfo); + CppToC(data.m_optionalFeatureSendBoardInfo, result.m_optionalFeatureSendBoardInfo, hermesData.m_pOptionalFeatureSendBoardInfo); + CppToC(data.m_optionalFeatureCommand, result.m_optionalFeatureCommand, hermesData.m_pOptionalFeatureCommand); + } + + inline void CToCpp(const HermesSupportedFeatures& data, SupportedFeatures& result) + { + CToCpp(data.m_pOptionalFeatureBoardForecast, result.m_optionalFeatureBoardForecast); + CToCpp(data.m_pOptionalFeatureCheckAliveResponse, result.m_optionalFeatureCheckAliveResponse); + CToCpp(data.m_pOptionalFeatureQueryBoardInfo, result.m_optionalFeatureQueryBoardInfo); + CToCpp(data.m_pOptionalFeatureSendBoardInfo, result.m_optionalFeatureSendBoardInfo); + CToCpp(data.m_pOptionalFeatureCommand, result.m_optionalFeatureCommand); + } + + // ServiceDescriptionData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - explicit Converter2C(const Error& data) + explicit Converter2C(const ServiceDescriptionData& data) { - CppToC(data.m_code, m_data.m_code); - CppToC(data.m_text, m_data.m_text); + CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_laneId, m_data.m_laneId); + CppToC(data.m_optionalInterfaceId, m_data.m_optionalInterfaceId); + CppToC(data.m_version, m_data.m_version); + CppToC(data.m_supportedFeatures, m_supportedFeatures); + m_data.m_pSupportedFeatures = &m_supportedFeatures.m_data; } + + private: + SupportedFeaturesHolder m_supportedFeatures; }; - inline Error ToCpp(const HermesError& data) + inline ServiceDescriptionData ToCpp(const HermesServiceDescriptionData& data) { - Error result; - CToCpp(data.m_code, result.m_code); - CToCpp(data.m_text, result.m_text); + ServiceDescriptionData result; + CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_laneId, result.m_laneId); + CToCpp(data.m_optionalInterfaceId, result.m_optionalInterfaceId); + CToCpp(data.m_version, result.m_version); + CToCpp(data.m_pSupportedFeatures, result.m_supportedFeatures); return result; } - // CheckAliveData + // MachineReadyData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - explicit Converter2C(const CheckAliveData& data) + explicit Converter2C(const MachineReadyData& data) { - CppToC(data.m_optionalType, m_optionalType, m_data.m_pOptionalType); - CppToC(data.m_optionalId, m_data.m_optionalId); + CppToC(data.m_optionalFlippedBoard, m_flippedBoard, m_data.m_pOptionalFlippedBoard); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalForecastId, m_data.m_optionalForecastId); + CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_optionalLengthInMM, m_data.m_pOptionalLengthInMM); + CppToC(data.m_optionalWidthInMM, m_data.m_pOptionalWidthInMM); + CppToC(data.m_optionalThicknessInMM, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_optionalTopClearanceHeightInMM, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_optionalBottomClearanceHeightInMM, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_optionalWeightInGrams, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); } + private: - EHermesCheckAliveType m_optionalType{}; + EHermesFlippedBoard m_flippedBoard; }; - inline CheckAliveData ToCpp(const HermesCheckAliveData& data) + + inline MachineReadyData ToCpp(const HermesMachineReadyData& data) { - CheckAliveData result; - CToCpp(data.m_pOptionalType, result.m_optionalType); - CToCpp(data.m_optionalId, result.m_optionalId); + MachineReadyData result; + CToCpp(data.m_failedBoard, result.m_failedBoard); + CToCpp(data.m_optionalForecastId, result.m_optionalForecastId); + CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); + CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); + CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_optionalBottomClearanceHeightInMM); + CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); + CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + return result; + } + + // RevokeMachineReadyData + template<> struct Converter2C : ConverterBase + { + explicit Converter2C(const RevokeMachineReadyData&) {} + }; + inline RevokeMachineReadyData ToCpp(const HermesRevokeMachineReadyData&) { return{}; } + + // SubBoards + inline void CppToC(const SubBoard& data, HermesSubBoard& result) + { + CppToC(data.m_pos, result.m_pos); + CppToC(data.m_optionalBc, result.m_optionalBc); + CppToC(data.m_st, result.m_st); + } + + inline void CToCpp(const HermesSubBoard& data, SubBoard& result) + { + CToCpp(data.m_pos, result.m_pos); + CToCpp(data.m_optionalBc, result.m_optionalBc); + CToCpp(data.m_st, result.m_st); + } + + // BoardAvailableData + template<> + struct Converter2C : ConverterBase + { + Converter2C(const BoardAvailableData& data) + { + CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_flippedBoard, m_data.m_flippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_optionalLengthInMM, m_data.m_pOptionalLengthInMM); + CppToC(data.m_optionalWidthInMM, m_data.m_pOptionalWidthInMM); + CppToC(data.m_optionalThicknessInMM, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_optionalTopClearanceHeightInMM, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_optionalBottomClearanceHeightInMM, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_optionalWeightInGrams, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_optionalRoute, m_data.m_pOptionalRoute); + CppToC(data.m_optionalAction, m_data.m_pOptionalAction); + CppToC(data.m_optionalSubBoards, m_subBoards, m_data.m_optionalSubBoards); + } + + private: + VectorHolder m_subBoards; + }; + inline BoardAvailableData ToCpp(const HermesBoardAvailableData& data) + { + BoardAvailableData result; + CToCpp(data.m_boardId, result.m_boardId); + CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); + CToCpp(data.m_failedBoard, result.m_failedBoard); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_flippedBoard, result.m_flippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); + CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); + CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_optionalBottomClearanceHeightInMM); + CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); + CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); + CToCpp(data.m_pOptionalAction, result.m_optionalAction); + CToCpp(data.m_optionalSubBoards, result.m_optionalSubBoards); + return result; + } + + // RevokeBoardAvailableData + template<> struct Converter2C : ConverterBase + { + explicit Converter2C(const RevokeBoardAvailableData&) {} + }; + inline RevokeBoardAvailableData ToCpp(const HermesRevokeBoardAvailableData&) { return{}; } + + // StartTransportData + template<> + struct Converter2C : ConverterBase + { + Converter2C(const StartTransportData& data) + { + CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + } + }; + inline StartTransportData ToCpp(const HermesStartTransportData& data) + { + StartTransportData result; + CToCpp(data.m_boardId, result.m_boardId); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); + return result; + } + + // StopTransportData + template<> + struct Converter2C : ConverterBase + { + Converter2C(const StopTransportData& data) + { + CppToC(data.m_transferState, m_data.m_transferState); + CppToC(data.m_boardId, m_data.m_boardId); + } + }; + inline StopTransportData ToCpp(const HermesStopTransportData& data) + { + StopTransportData result; + CToCpp(data.m_transferState, result.m_transferState); + CToCpp(data.m_boardId, result.m_boardId); + return result; + } + + // TransportFinishedData + template<> + struct Converter2C : ConverterBase + { + Converter2C(const TransportFinishedData& data) + { + CppToC(data.m_transferState, m_data.m_transferState); + CppToC(data.m_boardId, m_data.m_boardId); + } + }; + inline TransportFinishedData ToCpp(const HermesTransportFinishedData& data) + { + TransportFinishedData result; + CToCpp(data.m_transferState, result.m_transferState); + CToCpp(data.m_boardId, result.m_boardId); return result; } @@ -403,263 +597,485 @@ namespace Hermes template<> struct Converter2C : ConverterBase { - explicit Converter2C(const NotificationData& data) + Converter2C(const NotificationData& data) { CppToC(data.m_notificationCode, m_data.m_notificationCode); - CppToC(data.m_severity, m_data.m_severity); - CppToC(data.m_description, m_data.m_description); + CppToC(data.m_severity, m_data.m_severity); + CppToC(data.m_description, m_data.m_description); } }; inline NotificationData ToCpp(const HermesNotificationData& data) { NotificationData result; CToCpp(data.m_notificationCode, result.m_notificationCode); - CToCpp(data.m_severity, result.m_severity); - CToCpp(data.m_description, result.m_description); + CToCpp(data.m_severity, result.m_severity); + CToCpp(data.m_description, result.m_description); return result; } - // ServiceDescriptionData + // CheckAliveData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - explicit Converter2C(const ServiceDescriptionData& data) + Converter2C(const CheckAliveData& data) { - CppToC(data.m_machineId, m_data.m_machineId); - CppToC(data.m_laneId, m_data.m_laneId); - CppToC(data.m_optionalInterfaceId, m_data.m_optionalInterfaceId); - CppToC(data.m_version, m_data.m_version); - m_data.m_pSupportedFeatures = nullptr; // features are optional, not mapped here + CppToC(data.m_optionalType, m_optionalType, m_data.m_pOptionalType); + CppToC(data.m_optionalId, m_data.m_optionalId); } + + private: + EHermesCheckAliveType m_optionalType; }; - inline ServiceDescriptionData ToCpp(const HermesServiceDescriptionData& data) + inline CheckAliveData ToCpp(const HermesCheckAliveData& data) { - ServiceDescriptionData result; - CToCpp(data.m_machineId, result.m_machineId); - CToCpp(data.m_laneId, result.m_laneId); - CToCpp(data.m_optionalInterfaceId, result.m_optionalInterfaceId); - CToCpp(data.m_version, result.m_version); + CheckAliveData result; + CToCpp(data.m_pOptionalType, result.m_optionalType); + CToCpp(data.m_optionalId, result.m_optionalId); return result; } - // SubBoard / SubBoards helper - struct SubBoardsHolder + // BoardForecastData + template<> + struct Converter2C : ConverterBase { - VectorHolder m_holder; - HermesSubBoards m_data{}; + Converter2C(const BoardForecastData& data) + { + CppToC(data.m_optionalForecastId, m_data.m_optionalForecastId); + CppToC(data.m_optionalTimeUntilAvailableInSeconds, m_data.m_pOptionalTimeUntilAvailableInSeconds); + CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); + CppToC(data.m_optionalBoardIdCreatedBy, m_data.m_optionalBoardIdCreatedBy); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_flippedBoard, m_data.m_flippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_optionalLengthInMM, m_data.m_pOptionalLengthInMM); + CppToC(data.m_optionalWidthInMM, m_data.m_pOptionalWidthInMM); + CppToC(data.m_optionalThicknessInMM, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_optionalTopClearanceHeightInMM, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_optionalBottomClearanceHeightInMM, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_optionalWeightInGrams, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + } }; + inline BoardForecastData ToCpp(const HermesBoardForecastData& data) + { + BoardForecastData result; + CToCpp(data.m_optionalForecastId, result.m_optionalForecastId); + CToCpp(data.m_pOptionalTimeUntilAvailableInSeconds, result.m_optionalTimeUntilAvailableInSeconds); + CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); + CToCpp(data.m_optionalBoardIdCreatedBy, result.m_optionalBoardIdCreatedBy); + CToCpp(data.m_failedBoard, result.m_failedBoard); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_flippedBoard, result.m_flippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); + CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); + CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_optionalBottomClearanceHeightInMM); + CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); + CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + return result; + } - inline void CppToC(const SubBoard& data, HermesSubBoard& result) + // QueryBoardInfoData + template<> + struct Converter2C : ConverterBase { - CppToC(data.m_pos, result.m_pos); - CppToC(data.m_optionalBc, result.m_optionalBc); - CppToC(data.m_st, result.m_st); + Converter2C(const QueryBoardInfoData& data) + { + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + } + }; + inline QueryBoardInfoData ToCpp(const HermesQueryBoardInfoData& data) + { + QueryBoardInfoData result; + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + return result; } - inline void CToCpp(const HermesSubBoard& data, SubBoard& result) + + // SendBoardInfoData + template<> + struct Converter2C : ConverterBase { - CToCpp(data.m_pos, result.m_pos); - CToCpp(data.m_optionalBc, result.m_optionalBc); - CToCpp(data.m_st, result.m_st); + Converter2C(const SendBoardInfoData& data) + { + CppToC(data.m_optionalFailedBoard, m_optionalFailedBoard, m_data.m_pOptionalFailedBoard); + CppToC(data.m_optionalFlippedBoard, m_optionalFlippedBoard, m_data.m_pOptionalFlippedBoard); + + CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); + CppToC(data.m_optionalBoardIdCreatedBy, m_data.m_optionalBoardIdCreatedBy); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_optionalLengthInMM, m_data.m_pOptionalLengthInMM); + CppToC(data.m_optionalWidthInMM, m_data.m_pOptionalWidthInMM); + CppToC(data.m_optionalThicknessInMM, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_optionalTopClearanceHeightInMM, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_optionalBottomClearanceHeightInMM, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_optionalWeightInGrams, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_optionalRoute, m_data.m_pOptionalRoute); + CppToC(data.m_optionalAction, m_data.m_pOptionalAction); + CppToC(data.m_optionalSubBoards, m_subBoards, m_data.m_optionalSubBoards); + } + + private: + EHermesBoardQuality m_optionalFailedBoard; + EHermesFlippedBoard m_optionalFlippedBoard; + VectorHolder m_subBoards; + }; + + inline SendBoardInfoData ToCpp(const HermesSendBoardInfoData& data) + { + SendBoardInfoData result; + CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); + CToCpp(data.m_optionalBoardIdCreatedBy, result.m_optionalBoardIdCreatedBy); + CToCpp(data.m_pOptionalFailedBoard, result.m_optionalFailedBoard); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); + CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); + CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_optionalBottomClearanceHeightInMM); + CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); + CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); + CToCpp(data.m_pOptionalAction, result.m_optionalAction); + CToCpp(data.m_optionalSubBoards, result.m_optionalSubBoards); + return result; } - inline void CppToC(const std::vector& data, SubBoardsHolder& holder) + // SupervisoryFeatures + inline void CppToC(const FeatureConfiguration&, HermesFeatureConfiguration&) {} + inline void CppToC(const FeatureBoardTracking&, HermesFeatureBoardTracking&) {} + inline void CppToC(const FeatureQueryWorkOrderInfo&, HermesFeatureQueryWorkOrderInfo&) {} + inline void CppToC(const FeatureSendWorkOrderInfo&, HermesFeatureSendWorkOrderInfo&) {} + inline void CppToC(const FeatureReplyWorkOrderInfo&, HermesFeatureReplyWorkOrderInfo&) {} + inline void CppToC(const FeatureQueryHermesCapabilities&, HermesFeatureQueryHermesCapabilities&) {} + inline void CppToC(const FeatureSendHermesCapabilities&, HermesFeatureSendHermesCapabilities&) {} + + inline void CToCpp(const HermesFeatureConfiguration&, FeatureConfiguration&) {} + inline void CToCpp(const HermesFeatureBoardTracking&, FeatureBoardTracking&) {} + inline void CToCpp(const HermesFeatureQueryWorkOrderInfo&, FeatureQueryWorkOrderInfo&) {} + inline void CToCpp(const HermesFeatureSendWorkOrderInfo&, FeatureSendWorkOrderInfo&) {} + inline void CToCpp(const HermesFeatureReplyWorkOrderInfo&, FeatureReplyWorkOrderInfo&) {} + inline void CToCpp(const HermesFeatureQueryHermesCapabilities&, FeatureQueryHermesCapabilities&) {} + inline void CToCpp(const HermesFeatureSendHermesCapabilities&, FeatureSendHermesCapabilities&) {} + + // SupervisoryFeatures + struct SupervisoryFeaturesHolder : ConverterBase + { + HermesFeatureConfiguration m_optionalFeatureConfiguration; + HermesFeatureCheckAliveResponse m_optionalFeatureCheckAliveResponse; + HermesFeatureBoardTracking m_optionalFeatureBoardTracking; + HermesFeatureQueryWorkOrderInfo m_optionalFeatureQueryWorkOrderInfo; + HermesFeatureSendWorkOrderInfo m_optionalFeatureSendWorkOrderInfo; + HermesFeatureReplyWorkOrderInfo m_optionalFeatureReplyWorkOrderInfo; + HermesFeatureQueryHermesCapabilities m_optionalFeatureQueryHermesCapabilities; + HermesFeatureSendHermesCapabilities m_optionalFeatureSendHermesCapabilities; + }; + + inline void CppToC(const SupervisoryFeatures& data, SupervisoryFeaturesHolder& intermediate) { - CppToC(data, holder.m_holder, holder.m_data); + auto& hermesData = intermediate.m_data; + CppToC(data.m_optionalFeatureConfiguration, intermediate.m_optionalFeatureConfiguration, hermesData.m_pOptionalFeatureConfiguration); + CppToC(data.m_optionalFeatureCheckAliveResponse, intermediate.m_optionalFeatureCheckAliveResponse, hermesData.m_pOptionalFeatureCheckAliveResponse); + CppToC(data.m_optionalFeatureBoardTracking, intermediate.m_optionalFeatureBoardTracking, hermesData.m_pOptionalFeatureBoardTracking); + CppToC(data.m_optionalFeatureQueryWorkOrderInfo, intermediate.m_optionalFeatureQueryWorkOrderInfo, hermesData.m_pOptionalFeatureQueryWorkOrderInfo); + CppToC(data.m_optionalFeatureSendWorkOrderInfo, intermediate.m_optionalFeatureSendWorkOrderInfo, hermesData.m_pOptionalFeatureSendWorkOrderInfo); + CppToC(data.m_optionalFeatureReplyWorkOrderInfo, intermediate.m_optionalFeatureReplyWorkOrderInfo, hermesData.m_pOptionalFeatureReplyWorkOrderInfo); + CppToC(data.m_optionalFeatureQueryHermesCapabilities, intermediate.m_optionalFeatureQueryHermesCapabilities, hermesData.m_pOptionalFeatureQueryHermesCapabilities); + CppToC(data.m_optionalFeatureSendHermesCapabilities, intermediate.m_optionalFeatureSendHermesCapabilities, hermesData.m_pOptionalFeatureSendHermesCapabilities); } - inline void CToCpp(const HermesSubBoards& data, std::vector& result) + + inline void CToCpp(const HermesSupervisoryFeatures& data, SupervisoryFeatures& result) { - CToCpp(data, result); + CToCpp(data.m_pOptionalFeatureConfiguration, result.m_optionalFeatureConfiguration); + CToCpp(data.m_pOptionalFeatureCheckAliveResponse, result.m_optionalFeatureCheckAliveResponse); + CToCpp(data.m_pOptionalFeatureBoardTracking, result.m_optionalFeatureBoardTracking); + CToCpp(data.m_pOptionalFeatureQueryWorkOrderInfo, result.m_optionalFeatureQueryWorkOrderInfo); + CToCpp(data.m_pOptionalFeatureSendWorkOrderInfo, result.m_optionalFeatureSendWorkOrderInfo); + CToCpp(data.m_pOptionalFeatureReplyWorkOrderInfo, result.m_optionalFeatureReplyWorkOrderInfo); + CToCpp(data.m_pOptionalFeatureQueryHermesCapabilities, result.m_optionalFeatureQueryHermesCapabilities); + CToCpp(data.m_pOptionalFeatureSendHermesCapabilities, result.m_optionalFeatureSendHermesCapabilities); } - // BoardAvailableData + // SupervisoryServiceDescriptionData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - explicit Converter2C(const BoardAvailableData& data) + Converter2C(const SupervisoryServiceDescriptionData& data) { - CppToC(data.m_boardId, m_data.m_boardId); - CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_flippedBoard, m_data.m_flippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_length, m_optLength, m_data.m_pOptionalLengthInMM); - CppToC(data.m_width, m_optWidth, m_data.m_pOptionalWidthInMM); - CppToC(data.m_thickness, m_optThick, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_topClearanceHeight, m_optTopClear, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_bottomClearanceHeight, m_optBotClear, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_weight, m_optWeight, m_data.m_pOptionalWeightInGrams); - CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); - CppToC(data.m_optionalRoute, m_optRoute, m_data.m_pOptionalRoute); - CppToC(data.m_optionalAction, m_optAction, m_data.m_pOptionalAction); - CppToC(data.m_subBoards, m_subBoardsHolder); - m_data.m_optionalSubBoards = m_subBoardsHolder.m_data; + CppToC(data.m_systemId, m_data.m_systemId); + CppToC(data.m_version, m_data.m_version); + CppToC(data.m_supportedFeatures, m_supervisoryFeatures); + m_data.m_pSupportedFeatures = &m_supervisoryFeatures.m_data; } private: - double m_optLength{}, m_optWidth{}, m_optThick{}, m_optSpeed{}; - double m_optTopClear{}, m_optBotClear{}, m_optWeight{}; - uint16_t m_optRoute{}, m_optAction{}; - SubBoardsHolder m_subBoardsHolder; + SupervisoryFeaturesHolder m_supervisoryFeatures; }; - inline BoardAvailableData ToCpp(const HermesBoardAvailableData& data) + + // SupervisoryServiceDescriptionData + inline SupervisoryServiceDescriptionData ToCpp(const HermesSupervisoryServiceDescriptionData& data) { - BoardAvailableData result; - CToCpp(data.m_boardId, result.m_boardId); - CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); - CToCpp(data.m_failedBoard, result.m_failedBoard); - CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_flippedBoard, result.m_flippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_length); - CToCpp(data.m_pOptionalWidthInMM, result.m_width); - CToCpp(data.m_pOptionalThicknessInMM, result.m_thickness); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_topClearanceHeight); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_bottomClearanceHeight); - CToCpp(data.m_pOptionalWeightInGrams, result.m_weight); - CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); - CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); - CToCpp(data.m_pOptionalAction, result.m_optionalAction); - CToCpp(data.m_optionalSubBoards, result.m_subBoards); + SupervisoryServiceDescriptionData result; + CToCpp(data.m_systemId, result.m_systemId); + CToCpp(data.m_version, result.m_version); + CToCpp(data.m_pSupportedFeatures, result.m_supportedFeatures); return result; } - // RevokeBoardAvailableData - template<> struct Converter2C : ConverterBase - { explicit Converter2C(const RevokeBoardAvailableData&) {} }; - inline RevokeBoardAvailableData ToCpp(const HermesRevokeBoardAvailableData&) { return {}; } - - // MachineReadyData + // BoardArrivedData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - explicit Converter2C(const MachineReadyData& data) + Converter2C(const BoardArrivedData& data) { - CppToC(data.m_failedBoard, m_data.m_failedBoard); - CppToC(data.m_optionalForecastId, m_data.m_optionalForecastId); - CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); - CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); - CppToC(data.m_optionalFlippedBoard, m_optFlipped, m_data.m_pOptionalFlippedBoard); - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); - CppToC(data.m_length, m_optLength, m_data.m_pOptionalLengthInMM); - CppToC(data.m_width, m_optWidth, m_data.m_pOptionalWidthInMM); - CppToC(data.m_thickness, m_optThick, m_data.m_pOptionalThicknessInMM); - CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); - CppToC(data.m_topClearanceHeight, m_optTopClear, m_data.m_pOptionalTopClearanceHeightInMM); - CppToC(data.m_bottomClearanceHeight, m_optBotClear, m_data.m_pOptionalBottomClearanceHeightInMM); - CppToC(data.m_weight, m_optWeight, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_upstreamLaneId, m_data.m_upstreamLaneId); + CppToC(data.m_optionalUpstreamInterfaceId, m_data.m_optionalUpstreamInterfaceId); + CppToC(data.m_optionalMagazineId, m_data.m_optionalMagazineId); + CppToC(data.m_optionalSlotId, m_data.m_pOptionalSlotId); + CppToC(data.m_boardTransfer, m_data.m_boardTransfer); + CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_flippedBoard, m_data.m_flippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_optionalLengthInMM, m_data.m_pOptionalLengthInMM); + CppToC(data.m_optionalWidthInMM, m_data.m_pOptionalWidthInMM); + CppToC(data.m_optionalThicknessInMM, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_optionalTopClearanceHeightInMM, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_optionalBottomClearanceHeightInMM, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_optionalWeightInGrams, m_data.m_pOptionalWeightInGrams); CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); - CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_optionalRoute, m_data.m_pOptionalRoute); + CppToC(data.m_optionalAction, m_data.m_pOptionalAction); + CppToC(data.m_optionalSubBoards, m_subBoards, m_data.m_optionalSubBoards); } private: - EHermesFlippedBoard m_optFlipped{}; - double m_optLength{}, m_optWidth{}, m_optThick{}, m_optSpeed{}; - double m_optTopClear{}, m_optBotClear{}, m_optWeight{}; + VectorHolder m_subBoards; }; - inline MachineReadyData ToCpp(const HermesMachineReadyData& data) + inline BoardArrivedData ToCpp(const HermesBoardArrivedData& data) { - MachineReadyData result; - CToCpp(data.m_failedBoard, result.m_failedBoard); - CToCpp(data.m_optionalForecastId, result.m_optionalForecastId); - CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); + BoardArrivedData result; + CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_upstreamLaneId, result.m_upstreamLaneId); + CToCpp(data.m_optionalUpstreamInterfaceId, result.m_optionalUpstreamInterfaceId); + CToCpp(data.m_optionalMagazineId, result.m_optionalMagazineId); + CToCpp(data.m_pOptionalSlotId, result.m_optionalSlotId); + CToCpp(data.m_boardTransfer, result.m_boardTransfer); + CToCpp(data.m_boardId, result.m_boardId); + CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); + CToCpp(data.m_failedBoard, result.m_failedBoard); CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); - CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_flippedBoard, result.m_flippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); - CToCpp(data.m_pOptionalLengthInMM, result.m_length); - CToCpp(data.m_pOptionalWidthInMM, result.m_width); - CToCpp(data.m_pOptionalThicknessInMM, result.m_thickness); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); - CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_topClearanceHeight); - CToCpp(data.m_pOptionalBottomClearanceHeightInMM,result.m_bottomClearanceHeight); - CToCpp(data.m_pOptionalWeightInGrams, result.m_weight); - CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); - CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); + CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); + CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_optionalBottomClearanceHeightInMM); + CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); + CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); + CToCpp(data.m_pOptionalAction, result.m_optionalAction); + CToCpp(data.m_optionalSubBoards, result.m_optionalSubBoards); return result; } - // RevokeMachineReadyData - template<> struct Converter2C : ConverterBase - { explicit Converter2C(const RevokeMachineReadyData&) {} }; - inline RevokeMachineReadyData ToCpp(const HermesRevokeMachineReadyData&) { return {}; } - - // StartTransportData + // BoardDepartedData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - explicit Converter2C(const StartTransportData& data) + Converter2C(const BoardDepartedData& data) { - CppToC(data.m_boardId, m_data.m_boardId); - CppToC(data.m_conveyorSpeed, m_optSpeed, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_downstreamLaneId, m_data.m_downstreamLaneId); + CppToC(data.m_optionalDownstreamInterfaceId, m_data.m_optionalDownstreamInterfaceId); + CppToC(data.m_optionalMagazineId, m_data.m_optionalMagazineId); + CppToC(data.m_optionalSlotId, m_data.m_pOptionalSlotId); + CppToC(data.m_boardTransfer, m_data.m_boardTransfer); + CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_boardIdCreatedBy, m_data.m_boardIdCreatedBy); + CppToC(data.m_failedBoard, m_data.m_failedBoard); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_flippedBoard, m_data.m_flippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_optionalLengthInMM, m_data.m_pOptionalLengthInMM); + CppToC(data.m_optionalWidthInMM, m_data.m_pOptionalWidthInMM); + CppToC(data.m_optionalThicknessInMM, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_optionalTopClearanceHeightInMM, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_optionalBottomClearanceHeightInMM, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_optionalWeightInGrams, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_optionalRoute, m_data.m_pOptionalRoute); + CppToC(data.m_optionalAction, m_data.m_pOptionalAction); + CppToC(data.m_optionalSubBoards, m_subBoards, m_data.m_optionalSubBoards); } private: - double m_optSpeed{}; + VectorHolder m_subBoards; }; - inline StartTransportData ToCpp(const HermesStartTransportData& data) + + inline BoardDepartedData ToCpp(const HermesBoardDepartedData& data) { - StartTransportData result; - CToCpp(data.m_boardId, result.m_boardId); - CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_conveyorSpeed); + BoardDepartedData result; + + CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_downstreamLaneId, result.m_downstreamLaneId); + CToCpp(data.m_optionalDownstreamInterfaceId, result.m_optionalDownstreamInterfaceId); + CToCpp(data.m_optionalMagazineId, result.m_optionalMagazineId); + CToCpp(data.m_pOptionalSlotId, result.m_optionalSlotId); + CToCpp(data.m_boardTransfer, result.m_boardTransfer); + CToCpp(data.m_boardId, result.m_boardId); + CToCpp(data.m_boardIdCreatedBy, result.m_boardIdCreatedBy); + CToCpp(data.m_failedBoard, result.m_failedBoard); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_flippedBoard, result.m_flippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); + CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); + CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_optionalBottomClearanceHeightInMM); + CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); + CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); + CToCpp(data.m_pOptionalAction, result.m_optionalAction); + CToCpp(data.m_optionalSubBoards, result.m_optionalSubBoards); return result; } - // StopTransportData + // QueryWorkOrderInfoData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - explicit Converter2C(const StopTransportData& data) + Converter2C(const QueryWorkOrderInfoData& data) { - CppToC(data.m_transferState, m_data.m_transferState); - CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_optionalQueryId, m_data.m_optionalQueryId); + CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_optionalMagazineId, m_data.m_optionalMagazineId); + CppToC(data.m_optionalSlotId, m_data.m_pOptionalSlotId); + CppToC(data.m_optionalBarcode, m_data.m_optionalBarcode); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); } }; - inline StopTransportData ToCpp(const HermesStopTransportData& data) + inline QueryWorkOrderInfoData ToCpp(const HermesQueryWorkOrderInfoData& data) { - StopTransportData result; - CToCpp(data.m_transferState, result.m_transferState); - CToCpp(data.m_boardId, result.m_boardId); + QueryWorkOrderInfoData result; + CToCpp(data.m_optionalQueryId, result.m_optionalQueryId); + CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_optionalMagazineId, result.m_optionalMagazineId); + CToCpp(data.m_pOptionalSlotId, result.m_optionalSlotId); + CToCpp(data.m_optionalBarcode, result.m_optionalBarcode); + CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); return result; } - // TransportFinishedData + // SendWorkOrderInfoData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - explicit Converter2C(const TransportFinishedData& data) + Converter2C(const SendWorkOrderInfoData& data) { - CppToC(data.m_transferState, m_data.m_transferState); - CppToC(data.m_boardId, m_data.m_boardId); + CppToC(data.m_optionalQueryId, m_data.m_optionalQueryId); + CppToC(data.m_optionalWorkOrderId, m_data.m_optionalWorkOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_optionalBoardId, m_data.m_optionalBoardId); + CppToC(data.m_optionalBoardIdCreatedBy, m_data.m_optionalBoardIdCreatedBy); + CppToC(data.m_optionalFailedBoard, m_optionalFailedBoard, m_data.m_pOptionalFailedBoard); + CppToC(data.m_optionalProductTypeId, m_data.m_optionalProductTypeId); + CppToC(data.m_optionalFlippedBoard, m_optionalFlippedBoard, m_data.m_pOptionalFlippedBoard); + CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); + CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_optionalLengthInMM, m_data.m_pOptionalLengthInMM); + CppToC(data.m_optionalWidthInMM, m_data.m_pOptionalWidthInMM); + CppToC(data.m_optionalThicknessInMM, m_data.m_pOptionalThicknessInMM); + CppToC(data.m_optionalConveyorSpeedInMMPerSecs, m_data.m_pOptionalConveyorSpeedInMMPerSecs); + CppToC(data.m_optionalTopClearanceHeightInMM, m_data.m_pOptionalTopClearanceHeightInMM); + CppToC(data.m_optionalBottomClearanceHeightInMM, m_data.m_pOptionalBottomClearanceHeightInMM); + CppToC(data.m_optionalWeightInGrams, m_data.m_pOptionalWeightInGrams); + CppToC(data.m_optionalRoute, m_data.m_pOptionalRoute); + CppToC(data.m_optionalSubBoards, m_subBoards, m_data.m_optionalSubBoards); } + private: + VectorHolder m_subBoards; + EHermesBoardQuality m_optionalFailedBoard; + EHermesFlippedBoard m_optionalFlippedBoard; }; - inline TransportFinishedData ToCpp(const HermesTransportFinishedData& data) + inline SendWorkOrderInfoData ToCpp(const HermesSendWorkOrderInfoData& data) { - TransportFinishedData result; - CToCpp(data.m_transferState, result.m_transferState); - CToCpp(data.m_boardId, result.m_boardId); + SendWorkOrderInfoData result; + CToCpp(data.m_optionalQueryId, result.m_optionalQueryId); + CToCpp(data.m_optionalWorkOrderId, result.m_optionalWorkOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + CToCpp(data.m_optionalBoardId, result.m_optionalBoardId); + CToCpp(data.m_optionalBoardIdCreatedBy, result.m_optionalBoardIdCreatedBy); + CToCpp(data.m_pOptionalFailedBoard, result.m_optionalFailedBoard); + CToCpp(data.m_optionalProductTypeId, result.m_optionalProductTypeId); + CToCpp(data.m_pOptionalFlippedBoard, result.m_optionalFlippedBoard); + CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); + CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + CToCpp(data.m_pOptionalLengthInMM, result.m_optionalLengthInMM); + CToCpp(data.m_pOptionalWidthInMM, result.m_optionalWidthInMM); + CToCpp(data.m_pOptionalThicknessInMM, result.m_optionalThicknessInMM); + CToCpp(data.m_pOptionalConveyorSpeedInMMPerSecs, result.m_optionalConveyorSpeedInMMPerSecs); + CToCpp(data.m_pOptionalTopClearanceHeightInMM, result.m_optionalTopClearanceHeightInMM); + CToCpp(data.m_pOptionalBottomClearanceHeightInMM, result.m_optionalBottomClearanceHeightInMM); + CToCpp(data.m_pOptionalWeightInGrams, result.m_optionalWeightInGrams); + CToCpp(data.m_pOptionalRoute, result.m_optionalRoute); + CToCpp(data.m_optionalSubBoards, result.m_optionalSubBoards); return result; } - // QueryBoardInfoData + // ReplyWorkOrderInfoData template<> - struct Converter2C : ConverterBase + struct Converter2C : ConverterBase { - explicit Converter2C(const QueryBoardInfoData& data) + Converter2C(const ReplyWorkOrderInfoData& data) { - CppToC(data.m_optionalTopBarcode, m_data.m_optionalTopBarcode); - CppToC(data.m_optionalBottomBarcode, m_data.m_optionalBottomBarcode); + CppToC(data.m_workOrderId, m_data.m_workOrderId); + CppToC(data.m_optionalBatchId, m_data.m_optionalBatchId); + CppToC(data.m_status, m_data.m_status); } }; - inline QueryBoardInfoData ToCpp(const HermesQueryBoardInfoData& data) + inline ReplyWorkOrderInfoData ToCpp(const HermesReplyWorkOrderInfoData& data) { - QueryBoardInfoData result; - CToCpp(data.m_optionalTopBarcode, result.m_optionalTopBarcode); - CToCpp(data.m_optionalBottomBarcode, result.m_optionalBottomBarcode); + ReplyWorkOrderInfoData result; + CToCpp(data.m_workOrderId, result.m_workOrderId); + CToCpp(data.m_optionalBatchId, result.m_optionalBatchId); + CToCpp(data.m_status, result.m_status); return result; } @@ -667,7 +1083,7 @@ namespace Hermes template<> struct Converter2C : ConverterBase { - explicit Converter2C(const CommandData& data) + Converter2C(const CommandData& data) { CppToC(data.m_command, m_data.m_command); } @@ -679,31 +1095,172 @@ namespace Hermes return result; } + + // QueryHermesCapabilitiesData + template<> + struct Converter2C : ConverterBase + { + Converter2C(const QueryHermesCapabilitiesData&) + { + } + }; + inline QueryHermesCapabilitiesData ToCpp(const HermesQueryHermesCapabilitiesData&) + { + QueryHermesCapabilitiesData result; + return result; + } + + // OptionalMessages + inline void CppToC(const MessageCheckAliveResponse&, HermesMessageCheckAliveResponse&) {} + inline void CppToC(const MessageBoardForecast&, HermesMessageBoardForecast&) {} + inline void CppToC(const MessageQueryBoardInfo&, HermesMessageQueryBoardInfo&) {} + inline void CppToC(const MessageSendBoardInfo&, HermesMessageSendBoardInfo&) {} + inline void CppToC(const MessageBoardArrived&, HermesMessageBoardArrived&) {} + inline void CppToC(const MessageBoardDeparted&, HermesMessageBoardDeparted&) {} + inline void CppToC(const MessageQueryWorkOrderInfo&, HermesMessageQueryWorkOrderInfo&) {} + inline void CppToC(const MessageReplyWorkOrderInfo&, HermesMessageReplyWorkOrderInfo&) {} + inline void CppToC(const MessageCommand&, HermesMessageCommand&) {} + + inline void CToCpp(const HermesMessageCheckAliveResponse&, MessageCheckAliveResponse&) {} + inline void CToCpp(const HermesMessageBoardForecast&, MessageBoardForecast&) {} + inline void CToCpp(const HermesMessageQueryBoardInfo&, MessageQueryBoardInfo&) {} + inline void CToCpp(const HermesMessageSendBoardInfo&, MessageSendBoardInfo&) {} + inline void CToCpp(const HermesMessageBoardArrived&, MessageBoardArrived&) {} + inline void CToCpp(const HermesMessageBoardDeparted&, MessageBoardDeparted&) {} + inline void CToCpp(const HermesMessageQueryWorkOrderInfo&, MessageQueryWorkOrderInfo&) {} + inline void CToCpp(const HermesMessageReplyWorkOrderInfo&, MessageReplyWorkOrderInfo&) {} + inline void CToCpp(const HermesMessageCommand&, MessageCommand&) {} + + // OptionalMessages + struct OptionalMessagesHolder : ConverterBase + { + HermesMessageCheckAliveResponse m_optionalMessageCheckAliveResponse; + HermesMessageBoardForecast m_optionalMessageBoardForecast; + HermesMessageQueryBoardInfo m_optionalMessageQueryBoardInfo; + HermesMessageSendBoardInfo m_optionalMessageSendBoardInfo; + HermesMessageBoardArrived m_optionalMessageBoardArrived; + HermesMessageBoardDeparted m_optionalMessageBoardDeparted; + HermesMessageQueryWorkOrderInfo m_optionalMessageQueryWorkOrderInfo; + HermesMessageReplyWorkOrderInfo m_optionalMessageReplyWorkOrderInfo; + HermesMessageCommand m_optionalMessageCommand; + }; + + inline void CppToC(const OptionalMessages& data, OptionalMessagesHolder& result) + { + auto& hermesData = result.m_data; + CppToC(data.m_optionalMessageCheckAliveResponse, result.m_optionalMessageCheckAliveResponse, hermesData.m_pOptionalMessageCheckAliveResponse); + CppToC(data.m_optionalMessageBoardForecast, result.m_optionalMessageBoardForecast, hermesData.m_pOptionalMessageBoardForecast); + CppToC(data.m_optionalMessageQueryBoardInfo, result.m_optionalMessageQueryBoardInfo, hermesData.m_pOptionalMessageQueryBoardInfo); + CppToC(data.m_optionalMessageSendBoardInfo, result.m_optionalMessageSendBoardInfo, hermesData.m_pOptionalMessageSendBoardInfo); + CppToC(data.m_optionalMessageBoardArrived, result.m_optionalMessageBoardArrived, hermesData.m_pOptionalMessageBoardArrived); + CppToC(data.m_optionalMessageBoardDeparted, result.m_optionalMessageBoardDeparted, hermesData.m_pOptionalMessageBoardDeparted); + CppToC(data.m_optionalMessageQueryWorkOrderInfo, result.m_optionalMessageQueryWorkOrderInfo, hermesData.m_pOptionalMessageQueryWorkOrderInfo); + CppToC(data.m_optionalMessageReplyWorkOrderInfo, result.m_optionalMessageReplyWorkOrderInfo, hermesData.m_pOptionalMessageReplyWorkOrderInfo); + CppToC(data.m_optionalMessageCommand, result.m_optionalMessageCommand, hermesData.m_pOptionalMessageCommand); + } + + inline void CToCpp(const HermesOptionalMessages& data, OptionalMessages& result) + { + CToCpp(data.m_pOptionalMessageCheckAliveResponse, result.m_optionalMessageCheckAliveResponse); + CToCpp(data.m_pOptionalMessageBoardForecast, result.m_optionalMessageBoardForecast); + CToCpp(data.m_pOptionalMessageQueryBoardInfo, result.m_optionalMessageQueryBoardInfo); + CToCpp(data.m_pOptionalMessageSendBoardInfo, result.m_optionalMessageSendBoardInfo); + CToCpp(data.m_pOptionalMessageBoardArrived, result.m_optionalMessageBoardArrived); + CToCpp(data.m_pOptionalMessageBoardDeparted, result.m_optionalMessageBoardDeparted); + CToCpp(data.m_pOptionalMessageQueryWorkOrderInfo, result.m_optionalMessageQueryWorkOrderInfo); + CToCpp(data.m_pOptionalMessageReplyWorkOrderInfo, result.m_optionalMessageReplyWorkOrderInfo); + CToCpp(data.m_pOptionalMessageCommand, result.m_optionalMessageCommand); + } + + // Attributes + inline void CppToC(const Attributes& data, HermesAttributes& result) + { + CppToC(data.m_productTypeId, result.m_productTypeId); + CppToC(data.m_topBarcode, result.m_topBarcode); + CppToC(data.m_bottomBarcode, result.m_bottomBarcode); + CppToC(data.m_length, result.m_length); + CppToC(data.m_width, result.m_width); + CppToC(data.m_thickness, result.m_thickness); + CppToC(data.m_conveyorSpeed, result.m_conveyorSpeed); + CppToC(data.m_topClearanceHeight, result.m_topClearanceHeight); + CppToC(data.m_bottomClearanceHeight, result.m_bottomClearanceHeight); + CppToC(data.m_weight, result.m_weight); + CppToC(data.m_workOrderId, result.m_workOrderId); + CppToC(data.m_batchId, result.m_batchId); + CppToC(data.m_route, result.m_route); + CppToC(data.m_action, result.m_action); + CppToC(data.m_subBoards, result.m_subBoards); + } + + inline void CToCpp(const HermesAttributes& data, Attributes& result) + { + CToCpp(data.m_productTypeId, result.m_productTypeId); + CToCpp(data.m_topBarcode, result.m_topBarcode); + CToCpp(data.m_bottomBarcode, result.m_bottomBarcode); + CToCpp(data.m_length, result.m_length); + CToCpp(data.m_width, result.m_width); + CToCpp(data.m_thickness, result.m_thickness); + CToCpp(data.m_conveyorSpeed, result.m_conveyorSpeed); + CToCpp(data.m_topClearanceHeight, result.m_topClearanceHeight); + CToCpp(data.m_bottomClearanceHeight, result.m_bottomClearanceHeight); + CToCpp(data.m_weight, result.m_weight); + CToCpp(data.m_workOrderId, result.m_workOrderId); + CToCpp(data.m_batchId, result.m_batchId); + CToCpp(data.m_route, result.m_route); + CToCpp(data.m_action, result.m_action); + CToCpp(data.m_subBoards, result.m_subBoards); + } + + // SendHermesCapabilitiesData + template<> + struct Converter2C : ConverterBase + { + Converter2C(const SendHermesCapabilitiesData& data) + { + CppToC(data.m_optionalMessages, m_optionalMessages); + m_data.m_pOptionalMessages = &m_optionalMessages.m_data; + CppToC(data.m_attributes, m_hermesAttributes); + m_data.m_pAttributes = &m_hermesAttributes; + } + private: + OptionalMessagesHolder m_optionalMessages; + HermesAttributes m_hermesAttributes; + }; + + inline SendHermesCapabilitiesData ToCpp(const HermesSendHermesCapabilitiesData& data) + { + SendHermesCapabilitiesData result; + CToCpp(data.m_pOptionalMessages, result.m_optionalMessages); + CToCpp(data.m_pAttributes, result.m_attributes); + return result; + } + + // UpstreamSettings template<> struct Converter2C : ConverterBase { - explicit Converter2C(const UpstreamSettings& data) + Converter2C(const UpstreamSettings& data) { - CppToC(data.m_machineId, m_data.m_machineId); - CppToC(data.m_hostAddress, m_data.m_hostAddress); - CppToC(data.m_port, m_data.m_port); - CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); - CppToC(data.m_reconnectWaitTimeInSeconds, m_data.m_reconnectWaitTimeInSeconds); - CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); - CppToC(data.m_checkState, m_data.m_checkState); + CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_hostAddress, m_data.m_hostAddress); + CppToC(data.m_port, m_data.m_port); + CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); + CppToC(data.m_reconnectWaitTimeInSeconds, m_data.m_reconnectWaitTimeInSeconds); + CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); + CppToC(data.m_checkState, m_data.m_checkState); } }; inline UpstreamSettings ToCpp(const HermesUpstreamSettings& data) { UpstreamSettings result; - CToCpp(data.m_machineId, result.m_machineId); - CToCpp(data.m_hostAddress, result.m_hostAddress); - CToCpp(data.m_port, result.m_port); - CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); + CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_hostAddress, result.m_hostAddress); + CToCpp(data.m_port, result.m_port); + CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); CToCpp(data.m_reconnectWaitTimeInSeconds, result.m_reconnectWaitTimeInSeconds); - CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); - CToCpp(data.m_checkState, result.m_checkState); + CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); + CToCpp(data.m_checkState, result.m_checkState); return result; } @@ -711,27 +1268,27 @@ namespace Hermes template<> struct Converter2C : ConverterBase { - explicit Converter2C(const DownstreamSettings& data) + Converter2C(const DownstreamSettings& data) { - CppToC(data.m_machineId, m_data.m_machineId); - CppToC(data.m_optionalClientAddress, m_data.m_optionalClientAddress); - CppToC(data.m_port, m_data.m_port); - CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); + CppToC(data.m_machineId, m_data.m_machineId); + CppToC(data.m_optionalClientAddress, m_data.m_optionalClientAddress); + CppToC(data.m_port, m_data.m_port); + CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); CppToC(data.m_reconnectWaitTimeInSeconds, m_data.m_reconnectWaitTimeInSeconds); - CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); - CppToC(data.m_checkState, m_data.m_checkState); + CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); + CppToC(data.m_checkState, m_data.m_checkState); } }; inline DownstreamSettings ToCpp(const HermesDownstreamSettings& data) { DownstreamSettings result; - CToCpp(data.m_machineId, result.m_machineId); - CToCpp(data.m_optionalClientAddress, result.m_optionalClientAddress); - CToCpp(data.m_port, result.m_port); - CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); + CToCpp(data.m_machineId, result.m_machineId); + CToCpp(data.m_optionalClientAddress, result.m_optionalClientAddress); + CToCpp(data.m_port, result.m_port); + CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); CToCpp(data.m_reconnectWaitTimeInSeconds, result.m_reconnectWaitTimeInSeconds); - CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); - CToCpp(data.m_checkState, result.m_checkState); + CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); + CToCpp(data.m_checkState, result.m_checkState); return result; } @@ -739,16 +1296,16 @@ namespace Hermes template<> struct Converter2C : ConverterBase { - explicit Converter2C(const ConfigurationServiceSettings& data) + Converter2C(const ConfigurationServiceSettings& data) { - CppToC(data.m_port, m_data.m_port); + CppToC(data.m_port, m_data.m_port); CppToC(data.m_reconnectWaitTimeInSeconds, m_data.m_reconnectWaitTimeInSeconds); } }; inline ConfigurationServiceSettings ToCpp(const HermesConfigurationServiceSettings& data) { ConfigurationServiceSettings result; - CToCpp(data.m_port, result.m_port); + CToCpp(data.m_port, result.m_port); CToCpp(data.m_reconnectWaitTimeInSeconds, result.m_reconnectWaitTimeInSeconds); return result; } @@ -757,23 +1314,23 @@ namespace Hermes template<> struct Converter2C : ConverterBase { - explicit Converter2C(const VerticalServiceSettings& data) + Converter2C(const VerticalServiceSettings& data) { - CppToC(data.m_systemId, m_data.m_systemId); - CppToC(data.m_port, m_data.m_port); + CppToC(data.m_systemId, m_data.m_systemId); + CppToC(data.m_port, m_data.m_port); CppToC(data.m_reconnectWaitTimeInSeconds, m_data.m_reconnectWaitTimeInSeconds); - CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); - CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); + CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); + CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); } }; inline VerticalServiceSettings ToCpp(const HermesVerticalServiceSettings& data) { VerticalServiceSettings result; - CToCpp(data.m_systemId, result.m_systemId); - CToCpp(data.m_port, result.m_port); + CToCpp(data.m_systemId, result.m_systemId); + CToCpp(data.m_port, result.m_port); CToCpp(data.m_reconnectWaitTimeInSeconds, result.m_reconnectWaitTimeInSeconds); - CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); - CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); + CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); + CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); return result; } @@ -781,26 +1338,45 @@ namespace Hermes template<> struct Converter2C : ConverterBase { - explicit Converter2C(const VerticalClientSettings& data) + Converter2C(const VerticalClientSettings& data) { - CppToC(data.m_systemId, m_data.m_systemId); - CppToC(data.m_hostAddress, m_data.m_hostAddress); - CppToC(data.m_port, m_data.m_port); + CppToC(data.m_systemId, m_data.m_systemId); + CppToC(data.m_hostAddress, m_data.m_hostAddress); + CppToC(data.m_port, m_data.m_port); CppToC(data.m_reconnectWaitTimeInSeconds, m_data.m_reconnectWaitTimeInSeconds); - CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); - CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); + CppToC(data.m_checkAlivePeriodInSeconds, m_data.m_checkAlivePeriodInSeconds); + CppToC(data.m_checkAliveResponseMode, m_data.m_checkAliveResponseMode); } }; inline VerticalClientSettings ToCpp(const HermesVerticalClientSettings& data) { VerticalClientSettings result; - CToCpp(data.m_systemId, result.m_systemId); - CToCpp(data.m_hostAddress, result.m_hostAddress); - CToCpp(data.m_port, result.m_port); + CToCpp(data.m_systemId, result.m_systemId); + CToCpp(data.m_hostAddress, result.m_hostAddress); + CToCpp(data.m_port, result.m_port); CToCpp(data.m_reconnectWaitTimeInSeconds, result.m_reconnectWaitTimeInSeconds); - CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); - CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); + CToCpp(data.m_checkAlivePeriodInSeconds, result.m_checkAlivePeriodInSeconds); + CToCpp(data.m_checkAliveResponseMode, result.m_checkAliveResponseMode); return result; } -} // namespace Hermes + // Error + template<> + struct Converter2C : ConverterBase + { + Converter2C(const Error& data) + { + CppToC(data.m_code, m_data.m_code); + CppToC(data.m_text, m_data.m_text); + } + }; + inline Error ToCpp(const HermesError& data) + { + Error result; + CToCpp(data.m_code, result.m_code); + CToCpp(data.m_text, result.m_text); + return result; + } + + +} diff --git a/src/include/HermesModern.hpp b/src/include/HermesModern.hpp index 6946384..a2e42a9 100644 --- a/src/include/HermesModern.hpp +++ b/src/include/HermesModern.hpp @@ -1,57 +1,7 @@ -// ============================================================================= +// ============================================================================== // src/include/HermesModern.hpp -// Modern C++ wrapper for the Hermes Standard library. -// -// Wraps Hermes::Downstream and Hermes::Upstream behind std::function callbacks -// so callers register lambdas instead of implementing virtual interfaces. -// -// FIXES applied vs original: -// -// Modern::Downstream::InternalCallbackWrapper -// - Inherits Hermes::IDownstreamCallback (correct, was correct) -// - WRONG overrides removed: -// On(EState, BoardAvailableData) -> belongs to IUpstreamCallback -// On(EState, RevokeBoardAvailableData) -> belongs to IUpstreamCallback -// On(EState, TransportFinishedData) -> belongs to IUpstreamCallback -// On(EState, BoardForecastData) -> belongs to IUpstreamCallback -// On(EState, SendBoardInfoData) -> belongs to IUpstreamCallback -// - MISSING overrides added (were pure virtual, caused link failure): -// On(EState, MachineReadyData) = 0 in IDownstreamCallback -// On(EState, RevokeMachineReadyData) = 0 in IDownstreamCallback -// On(EState, StartTransportData) = 0 in IDownstreamCallback -// On(EState, StopTransportData) = 0 in IDownstreamCallback -// - WRONG callback types removed from public API: -// BoardAvailableCallback, RevokeBoardAvailableCallback, -// TransportFinishedCallback, BoardForecastCallback, SendBoardInfoCallback -// - CORRECT callback types added to public API: -// MachineReadyCallback, RevokeMachineReadyCallback, -// StartTransportCallback, StopTransportCallback, QueryBoardInfoCallback -// -// Modern::Upstream::InternalCallbackWrapper -// - Inherits Hermes::IUpstreamCallback (correct, was correct) -// - WRONG overrides removed: -// On(EState, MachineReadyData) -> belongs to IDownstreamCallback -// On(EState, RevokeMachineReadyData) -> belongs to IDownstreamCallback -// On(EState, StartTransportData) -> belongs to IDownstreamCallback -// On(EState, StopTransportData) -> belongs to IDownstreamCallback -// On(EState, QueryBoardInfoData) -> belongs to IDownstreamCallback -// - MISSING overrides added (were pure virtual): -// On(EState, BoardAvailableData) = 0 in IUpstreamCallback -// On(EState, RevokeBoardAvailableData) = 0 in IUpstreamCallback -// On(EState, TransportFinishedData) = 0 in IUpstreamCallback -// - WRONG callback types removed from public API: -// MachineReadyCallback, RevokeMachineReadyCallback, -// StartTransportCallback, StopTransportCallback, QueryBoardInfoCallback -// - CORRECT callback types added to public API: -// BoardAvailableCallback, RevokeBoardAvailableCallback, -// TransportFinishedCallback, BoardForecastCallback, SendBoardInfoCallback -// -// Both classes: -// - Stop() is now safe to call multiple times (guarded by exchange) -// - Enable() cannot be called twice without Stop() in between -// - m_laneId stored but unused warning removed (used in construction) -// ============================================================================= - +// Modern C++ Wrapper for the Hermes Standard Library (Complete Suite) +// ============================================================================== #pragma once #include "Hermes.hpp" @@ -63,409 +13,510 @@ namespace Hermes { namespace Modern { -// ============================================================================= -// Modern::Downstream -// -// Listens on a TCP port for an incoming Upstream machine connection. -// Receives messages that the Upstream machine sends: -// ServiceDescription, MachineReady, RevokeMachineReady, -// StartTransport, StopTransport, QueryBoardInfo -// -// Sends messages that the Downstream machine produces: -// ServiceDescription, BoardAvailable, RevokeBoardAvailable, -// TransportFinished, BoardForecast, SendBoardInfo, -// Notification, CheckAlive, Command -// ============================================================================= -class Downstream -{ +// ============================================================================== +// Downstream Wrapper (Receiver - Horizontal) +// ============================================================================== +class Downstream { public: - // --- Connection lifecycle callbacks --- - using ConnectedCallback = std::function; - using DisconnectedCallback = std::function; - using StateChangeCallback = std::function; - using TraceCallback = std::function; - - // --- Messages RECEIVED from the Upstream machine --- - using ServiceDescriptionCallback = std::function; - using MachineReadyCallback = std::function; - using RevokeMachineReadyCallback = std::function; - using StartTransportCallback = std::function; - using StopTransportCallback = std::function; - using QueryBoardInfoCallback = std::function; - - // --- Auxiliary messages (either direction) --- - using NotificationCallback = std::function; - using CheckAliveCallback = std::function; - using CommandCallback = std::function; - - explicit Downstream(unsigned laneId) - : m_isRunning(false) - { + using ConnectedCallback = std::function; + using DisconnectedCallback = std::function; + using StateChangeCallback = std::function; + using TraceCallback = std::function; + + using ServiceDescriptionCallback = std::function; + using MachineReadyCallback = std::function; + using RevokeMachineReadyCallback = std::function; + using StartTransportCallback = std::function; + using StopTransportCallback = std::function; + using QueryBoardInfoCallback = std::function; + + using NotificationCallback = std::function; + using CheckAliveCallback = std::function; + using CommandCallback = std::function; + + Downstream(unsigned laneId) : m_laneId(laneId), m_isRunning(false) { m_callbackWrapper = std::make_unique(this); - m_downstream = std::make_unique(laneId, *m_callbackWrapper); + m_downstream = std::make_unique(laneId, *m_callbackWrapper); } ~Downstream() { Stop(); } - Downstream(const Downstream&) = delete; - Downstream& operator=(const Downstream&) = delete; - - // --- Register callbacks (call before Enable) --- - void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = std::move(cb); } - void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = std::move(cb); } - void RegisterStateChangeCallback(StateChangeCallback cb) { m_onStateChange = std::move(cb); } - void RegisterTraceCallback(TraceCallback cb) { m_onTrace = std::move(cb); } - void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb){ m_onServiceDescription = std::move(cb); } - void RegisterMachineReadyCallback(MachineReadyCallback cb) { m_onMachineReady = std::move(cb); } - void RegisterRevokeMachineReadyCallback(RevokeMachineReadyCallback cb){ m_onRevokeMachineReady = std::move(cb); } - void RegisterStartTransportCallback(StartTransportCallback cb) { m_onStartTransport = std::move(cb); } - void RegisterStopTransportCallback(StopTransportCallback cb) { m_onStopTransport = std::move(cb); } - void RegisterQueryBoardInfoCallback(QueryBoardInfoCallback cb) { m_onQueryBoardInfo = std::move(cb); } - void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = std::move(cb); } - void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = std::move(cb); } - void RegisterCommandCallback(CommandCallback cb) { m_onCommand = std::move(cb); } - - // --- Lifecycle --- - - // Enable starts listening and launches the network thread. - // Call this after registering callbacks. - void Enable(const DownstreamSettings& settings) - { - if (m_isRunning.exchange(true)) - return; // already running - + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } + void RegisterStateChangeCallback(StateChangeCallback cb) { m_onStateChange = cb; } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } + + void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb) { m_onServiceDescription = cb; } + void RegisterMachineReadyCallback(MachineReadyCallback cb) { m_onMachineReady = cb; } + void RegisterRevokeMachineReadyCallback(RevokeMachineReadyCallback cb) { m_onRevokeMachineReady = cb; } + void RegisterStartTransportCallback(StartTransportCallback cb) { m_onStartTransport = cb; } + void RegisterStopTransportCallback(StopTransportCallback cb) { m_onStopTransport = cb; } + void RegisterQueryBoardInfoCallback(QueryBoardInfoCallback cb) { m_onQueryBoardInfo = cb; } + void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } + void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } + void RegisterCommandCallback(CommandCallback cb) { m_onCommand = cb; } + + void Enable(const DownstreamSettings& settings) { + if (m_isRunning) return; m_downstream->Enable(settings); + m_isRunning = true; m_networkThread = std::thread([this]() { m_downstream->Run(); }); } - // Stop shuts down the connection and joins the network thread. - // Safe to call multiple times. - void Stop() - { - if (!m_isRunning.exchange(false)) - return; // already stopped - - m_downstream->Stop(); - if (m_networkThread.joinable()) - m_networkThread.join(); + void Stop() { + if (m_isRunning) { + m_downstream->Stop(); + if (m_networkThread.joinable()) m_networkThread.join(); + m_isRunning = false; + } } - // --- Send messages TO the Upstream machine --- - // Must be called from within a Post() lambda or a callback to be thread-safe. template void Signal(unsigned sessionId, const T& data) { m_downstream->Signal(sessionId, data); } - // Post a callable onto the Hermes network thread (thread-safe). - template - void Post(F&& f) { m_downstream->Post(std::forward(f)); } - private: - // ------------------------------------------------------------------------- - // InternalCallbackWrapper - // Implements Hermes::IDownstreamCallback and forwards to std::function members. - // Pure virtuals from IDownstreamCallback that MUST be overridden: - // OnConnected, OnDisconnected, OnState, OnTrace - // On(EState, ServiceDescriptionData) - // On(EState, MachineReadyData) - // On(EState, RevokeMachineReadyData) - // On(EState, StartTransportData) - // On(EState, StopTransportData) - // On(NotificationData) - // On(CommandData) - // ------------------------------------------------------------------------- - struct InternalCallbackWrapper : Hermes::IDownstreamCallback - { - explicit InternalCallbackWrapper(Downstream* parent) : m_parent(parent) {} + class InternalCallbackWrapper : public Hermes::IDownstreamCallback { + Downstream* m_parent; + public: + InternalCallbackWrapper(Downstream* parent) : m_parent(parent) {} - void OnConnected(unsigned sessionId, EState state, const ConnectionInfo& info) override - { - if (m_parent->m_onConnected) m_parent->m_onConnected(sessionId, info); + void OnConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { + if (m_parent->m_onConnected) m_parent->m_onConnected(info); } - - void OnDisconnected(unsigned sessionId, EState, const Error& error) override - { - if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(sessionId, error); + void OnDisconnected(unsigned sessionId, Hermes::EState state, const Hermes::Error& error) override { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error); } - - void OnState(unsigned sessionId, EState state) override - { - if (m_parent->m_onStateChange) m_parent->m_onStateChange(sessionId, state); + void OnState(unsigned sessionId, Hermes::EState state) override { + if (m_parent->m_onStateChange) m_parent->m_onStateChange(state); } - - void OnTrace(unsigned sessionId, ETraceType type, StringView trace) override - { - if (m_parent->m_onTrace) m_parent->m_onTrace(sessionId, type, trace); + void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { + if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); } - // Messages received FROM the Upstream machine: - void On(unsigned sessionId, EState state, const ServiceDescriptionData& data) override - { - if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(sessionId, state, data); + void On(unsigned sessionId, Hermes::EState state, const Hermes::ServiceDescriptionData& data) override { + if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(data); } - - void On(unsigned sessionId, EState state, const MachineReadyData& data) override - { - if (m_parent->m_onMachineReady) m_parent->m_onMachineReady(sessionId, state, data); + void On(unsigned sessionId, Hermes::EState state, const Hermes::MachineReadyData& data) override { + if (m_parent->m_onMachineReady) m_parent->m_onMachineReady(data); } - - void On(unsigned sessionId, EState state, const RevokeMachineReadyData& data) override - { - if (m_parent->m_onRevokeMachineReady) m_parent->m_onRevokeMachineReady(sessionId, state, data); + void On(unsigned sessionId, Hermes::EState state, const Hermes::RevokeMachineReadyData& data) override { + if (m_parent->m_onRevokeMachineReady) m_parent->m_onRevokeMachineReady(data); } - - void On(unsigned sessionId, EState state, const StartTransportData& data) override - { - if (m_parent->m_onStartTransport) m_parent->m_onStartTransport(sessionId, state, data); + void On(unsigned sessionId, Hermes::EState state, const Hermes::StartTransportData& data) override { + if (m_parent->m_onStartTransport) m_parent->m_onStartTransport(data); } - - void On(unsigned sessionId, EState state, const StopTransportData& data) override - { - if (m_parent->m_onStopTransport) m_parent->m_onStopTransport(sessionId, state, data); + void On(unsigned sessionId, Hermes::EState state, const Hermes::StopTransportData& data) override { + if (m_parent->m_onStopTransport) m_parent->m_onStopTransport(data); } - - void On(unsigned sessionId, const QueryBoardInfoData& data) override - { - if (m_parent->m_onQueryBoardInfo) m_parent->m_onQueryBoardInfo(sessionId, data); + void On(unsigned sessionId, const Hermes::QueryBoardInfoData& data) override { + if (m_parent->m_onQueryBoardInfo) m_parent->m_onQueryBoardInfo(data); } - - // Auxiliary messages: - void On(unsigned sessionId, const NotificationData& data) override - { - if (m_parent->m_onNotification) m_parent->m_onNotification(sessionId, data); + void On(unsigned sessionId, const Hermes::NotificationData& data) override { + if (m_parent->m_onNotification) m_parent->m_onNotification(data); } - - void On(unsigned sessionId, const CheckAliveData& data) override - { - if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(sessionId, data); + void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); } - - void On(unsigned sessionId, const CommandData& data) override - { - if (m_parent->m_onCommand) m_parent->m_onCommand(sessionId, data); + void On(unsigned sessionId, const Hermes::CommandData& data) override { + if (m_parent->m_onCommand) m_parent->m_onCommand(data); } - - Downstream* m_parent; }; - std::atomic m_isRunning; - std::thread m_networkThread; + unsigned m_laneId; + std::atomic m_isRunning; + std::thread m_networkThread; std::unique_ptr m_callbackWrapper; - std::unique_ptr m_downstream; - - ConnectedCallback m_onConnected; - DisconnectedCallback m_onDisconnected; - StateChangeCallback m_onStateChange; - TraceCallback m_onTrace; - ServiceDescriptionCallback m_onServiceDescription; - MachineReadyCallback m_onMachineReady; - RevokeMachineReadyCallback m_onRevokeMachineReady; - StartTransportCallback m_onStartTransport; - StopTransportCallback m_onStopTransport; - QueryBoardInfoCallback m_onQueryBoardInfo; - NotificationCallback m_onNotification; - CheckAliveCallback m_onCheckAlive; - CommandCallback m_onCommand; + std::unique_ptr m_downstream; + + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + StateChangeCallback m_onStateChange; + TraceCallback m_onTrace; + + ServiceDescriptionCallback m_onServiceDescription; + MachineReadyCallback m_onMachineReady; + RevokeMachineReadyCallback m_onRevokeMachineReady; + StartTransportCallback m_onStartTransport; + StopTransportCallback m_onStopTransport; + QueryBoardInfoCallback m_onQueryBoardInfo; + + NotificationCallback m_onNotification; + CheckAliveCallback m_onCheckAlive; + CommandCallback m_onCommand; }; -// ============================================================================= -// Modern::Upstream -// -// Connects to a Downstream machine's TCP port. -// Receives messages that the Downstream machine sends: -// ServiceDescription, BoardAvailable, RevokeBoardAvailable, -// TransportFinished, BoardForecast, SendBoardInfo -// -// Sends messages that the Upstream machine produces: -// ServiceDescription, MachineReady, RevokeMachineReady, -// StartTransport, StopTransport, QueryBoardInfo, -// Notification, CheckAlive, Command -// ============================================================================= -class Upstream -{ +// ============================================================================== +// Upstream Wrapper (Sender - Horizontal) +// ============================================================================== +class Upstream { public: - // --- Connection lifecycle callbacks --- - using ConnectedCallback = std::function; - using DisconnectedCallback = std::function; - using StateChangeCallback = std::function; - using TraceCallback = std::function; - - // --- Messages RECEIVED from the Downstream machine --- - using ServiceDescriptionCallback = std::function; - using BoardAvailableCallback = std::function; - using RevokeBoardAvailableCallback = std::function; - using TransportFinishedCallback = std::function; - using BoardForecastCallback = std::function; - using SendBoardInfoCallback = std::function; - - // --- Auxiliary messages (either direction) --- - using NotificationCallback = std::function; - using CheckAliveCallback = std::function; - using CommandCallback = std::function; - - explicit Upstream(unsigned laneId) - : m_isRunning(false) - { + using ConnectedCallback = std::function; + using DisconnectedCallback = std::function; + using StateChangeCallback = std::function; + using TraceCallback = std::function; + + using ServiceDescriptionCallback = std::function; + using BoardAvailableCallback = std::function; + using RevokeBoardAvailableCallback = std::function; + using TransportFinishedCallback = std::function; + using BoardForecastCallback = std::function; + using SendBoardInfoCallback = std::function; + + using NotificationCallback = std::function; + using CheckAliveCallback = std::function; + using CommandCallback = std::function; + + Upstream(unsigned laneId) : m_laneId(laneId), m_isRunning(false) { m_callbackWrapper = std::make_unique(this); - m_upstream = std::make_unique(laneId, *m_callbackWrapper); + m_upstream = std::make_unique(laneId, *m_callbackWrapper); } ~Upstream() { Stop(); } - Upstream(const Upstream&) = delete; - Upstream& operator=(const Upstream&) = delete; - - // --- Register callbacks (call before Enable) --- - void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = std::move(cb); } - void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = std::move(cb); } - void RegisterStateChangeCallback(StateChangeCallback cb) { m_onStateChange = std::move(cb); } - void RegisterTraceCallback(TraceCallback cb) { m_onTrace = std::move(cb); } - void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb) { m_onServiceDescription = std::move(cb); } - void RegisterBoardAvailableCallback(BoardAvailableCallback cb) { m_onBoardAvailable = std::move(cb); } - void RegisterRevokeBoardAvailableCallback(RevokeBoardAvailableCallback cb){ m_onRevokeBoardAvailable = std::move(cb); } - void RegisterTransportFinishedCallback(TransportFinishedCallback cb) { m_onTransportFinished = std::move(cb); } - void RegisterBoardForecastCallback(BoardForecastCallback cb) { m_onBoardForecast = std::move(cb); } - void RegisterSendBoardInfoCallback(SendBoardInfoCallback cb) { m_onSendBoardInfo = std::move(cb); } - void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = std::move(cb); } - void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = std::move(cb); } - void RegisterCommandCallback(CommandCallback cb) { m_onCommand = std::move(cb); } - - // --- Lifecycle --- - - // Enable connects to the downstream host and launches the network thread. - void Enable(const UpstreamSettings& settings) - { - if (m_isRunning.exchange(true)) - return; - + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } + void RegisterStateChangeCallback(StateChangeCallback cb) { m_onStateChange = cb; } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } + + void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb) { m_onServiceDescription = cb; } + void RegisterBoardAvailableCallback(BoardAvailableCallback cb) { m_onBoardAvailable = cb; } + void RegisterRevokeBoardAvailableCallback(RevokeBoardAvailableCallback cb) { m_onRevokeBoardAvailable = cb; } + void RegisterTransportFinishedCallback(TransportFinishedCallback cb) { m_onTransportFinished = cb; } + void RegisterBoardForecastCallback(BoardForecastCallback cb) { m_onBoardForecast = cb; } + void RegisterSendBoardInfoCallback(SendBoardInfoCallback cb) { m_onSendBoardInfo = cb; } + void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } + void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } + void RegisterCommandCallback(CommandCallback cb) { m_onCommand = cb; } + + void Enable(const UpstreamSettings& settings) { + if (m_isRunning) return; m_upstream->Enable(settings); + m_isRunning = true; m_networkThread = std::thread([this]() { m_upstream->Run(); }); } - // Stop disconnects and joins the network thread. Safe to call multiple times. - void Stop() - { - if (!m_isRunning.exchange(false)) - return; - - m_upstream->Stop(); - if (m_networkThread.joinable()) - m_networkThread.join(); + void Stop() { + if (m_isRunning) { + m_upstream->Stop(); + if (m_networkThread.joinable()) m_networkThread.join(); + m_isRunning = false; + } } - // --- Send messages TO the Downstream machine --- template void Signal(unsigned sessionId, const T& data) { m_upstream->Signal(sessionId, data); } - // Post a callable onto the Hermes network thread (thread-safe). - template - void Post(F&& f) { m_upstream->Post(std::forward(f)); } - private: - // ------------------------------------------------------------------------- - // InternalCallbackWrapper - // Implements Hermes::IUpstreamCallback and forwards to std::function members. - // Pure virtuals from IUpstreamCallback that MUST be overridden: - // OnConnected, OnDisconnected, OnState, OnTrace - // On(EState, ServiceDescriptionData) - // On(EState, BoardAvailableData) - // On(EState, RevokeBoardAvailableData) - // On(EState, TransportFinishedData) - // On(NotificationData) - // On(CommandData) - // ------------------------------------------------------------------------- - struct InternalCallbackWrapper : Hermes::IUpstreamCallback - { - explicit InternalCallbackWrapper(Upstream* parent) : m_parent(parent) {} + class InternalCallbackWrapper : public Hermes::IUpstreamCallback { + Upstream* m_parent; + public: + InternalCallbackWrapper(Upstream* parent) : m_parent(parent) {} - void OnConnected(unsigned sessionId, EState state, const ConnectionInfo& info) override - { - if (m_parent->m_onConnected) m_parent->m_onConnected(sessionId, info); + void OnConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { + if (m_parent->m_onConnected) m_parent->m_onConnected(info); } - - void OnDisconnected(unsigned sessionId, EState, const Error& error) override - { - if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(sessionId, error); + void OnDisconnected(unsigned sessionId, Hermes::EState state, const Hermes::Error& error) override { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error); } - - void OnState(unsigned sessionId, EState state) override - { - if (m_parent->m_onStateChange) m_parent->m_onStateChange(sessionId, state); + void OnState(unsigned sessionId, Hermes::EState state) override { + if (m_parent->m_onStateChange) m_parent->m_onStateChange(state); } - - void OnTrace(unsigned sessionId, ETraceType type, StringView trace) override - { - if (m_parent->m_onTrace) m_parent->m_onTrace(sessionId, type, trace); + void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { + if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); } - // Messages received FROM the Downstream machine: - void On(unsigned sessionId, EState state, const ServiceDescriptionData& data) override - { - if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(sessionId, state, data); + void On(unsigned sessionId, Hermes::EState state, const Hermes::ServiceDescriptionData& data) override { + if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(data); } - - void On(unsigned sessionId, EState state, const BoardAvailableData& data) override - { - if (m_parent->m_onBoardAvailable) m_parent->m_onBoardAvailable(sessionId, state, data); + void On(unsigned sessionId, Hermes::EState state, const Hermes::BoardAvailableData& data) override { + if (m_parent->m_onBoardAvailable) m_parent->m_onBoardAvailable(data); } - - void On(unsigned sessionId, EState state, const RevokeBoardAvailableData& data) override - { - if (m_parent->m_onRevokeBoardAvailable) m_parent->m_onRevokeBoardAvailable(sessionId, state, data); + void On(unsigned sessionId, Hermes::EState state, const Hermes::RevokeBoardAvailableData& data) override { + if (m_parent->m_onRevokeBoardAvailable) m_parent->m_onRevokeBoardAvailable(data); } - - void On(unsigned sessionId, EState state, const TransportFinishedData& data) override - { - if (m_parent->m_onTransportFinished) m_parent->m_onTransportFinished(sessionId, state, data); + void On(unsigned sessionId, Hermes::EState state, const Hermes::TransportFinishedData& data) override { + if (m_parent->m_onTransportFinished) m_parent->m_onTransportFinished(data); + } + void On(unsigned sessionId, Hermes::EState state, const Hermes::BoardForecastData& data) override { + if (m_parent->m_onBoardForecast) m_parent->m_onBoardForecast(data); + } + void On(unsigned sessionId, const Hermes::SendBoardInfoData& data) override { + if (m_parent->m_onSendBoardInfo) m_parent->m_onSendBoardInfo(data); + } + void On(unsigned sessionId, const Hermes::NotificationData& data) override { + if (m_parent->m_onNotification) m_parent->m_onNotification(data); + } + void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); } + void On(unsigned sessionId, const Hermes::CommandData& data) override { + if (m_parent->m_onCommand) m_parent->m_onCommand(data); + } + }; + + unsigned m_laneId; + std::atomic m_isRunning; + std::thread m_networkThread; + std::unique_ptr m_callbackWrapper; + std::unique_ptr m_upstream; + + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + StateChangeCallback m_onStateChange; + TraceCallback m_onTrace; + + ServiceDescriptionCallback m_onServiceDescription; + BoardAvailableCallback m_onBoardAvailable; + RevokeBoardAvailableCallback m_onRevokeBoardAvailable; + TransportFinishedCallback m_onTransportFinished; + BoardForecastCallback m_onBoardForecast; + SendBoardInfoCallback m_onSendBoardInfo; + + NotificationCallback m_onNotification; + CheckAliveCallback m_onCheckAlive; + CommandCallback m_onCommand; +}; + + +// ============================================================================== +// VerticalService Wrapper (Machine Server - Vertical) +// ============================================================================== +class VerticalService { +public: + using ConnectedCallback = std::function; + using DisconnectedCallback = std::function; + using TraceCallback = std::function; + + using SupervisoryServiceDescriptionCallback = std::function; + using GetConfigurationCallback = std::function; + using SetConfigurationCallback = std::function; + using SendWorkOrderInfoCallback = std::function; + using QueryHermesCapabilitiesCallback = std::function; + + using NotificationCallback = std::function; + using CheckAliveCallback = std::function; + + VerticalService() : m_isRunning(false) { + m_callbackWrapper = std::make_unique(this); + m_verticalService = std::make_unique(*m_callbackWrapper); + } - void On(unsigned sessionId, EState state, const BoardForecastData& data) override - { - if (m_parent->m_onBoardForecast) m_parent->m_onBoardForecast(sessionId, state, data); + ~VerticalService() { Stop(); } + + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } + + void RegisterSupervisoryServiceDescriptionCallback(SupervisoryServiceDescriptionCallback cb) { m_onSupervisoryServiceDescription = cb; } + void RegisterGetConfigurationCallback(GetConfigurationCallback cb) { m_onGetConfiguration = cb; } + void RegisterSetConfigurationCallback(SetConfigurationCallback cb) { m_onSetConfiguration = cb; } + void RegisterSendWorkOrderInfoCallback(SendWorkOrderInfoCallback cb) { m_onSendWorkOrderInfo = cb; } + void RegisterQueryHermesCapabilitiesCallback(QueryHermesCapabilitiesCallback cb) { m_onQueryHermesCapabilities = cb; } + void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } + void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } + + void Enable(const VerticalServiceSettings& settings) { + if (m_isRunning) return; + m_verticalService->Enable(settings); + m_isRunning = true; + m_networkThread = std::thread([this]() { m_verticalService->Run(); }); + } + + void Stop() { + if (m_isRunning) { + m_verticalService->Stop(); + if (m_networkThread.joinable()) m_networkThread.join(); + m_isRunning = false; } + } + + template + void Signal(unsigned sessionId, const T& data) { m_verticalService->Signal(sessionId, data); } + + template + void SignalBroadcast(const T& data) { m_verticalService->Signal(data); } - void On(unsigned sessionId, const SendBoardInfoData& data) override - { - if (m_parent->m_onSendBoardInfo) m_parent->m_onSendBoardInfo(sessionId, data); +private: + class InternalCallbackWrapper : public Hermes::IVerticalServiceCallback { + VerticalService* m_parent; + public: + InternalCallbackWrapper(VerticalService* parent) : m_parent(parent) {} + + void OnConnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::ConnectionInfo& info) override { + if (m_parent->m_onConnected) m_parent->m_onConnected(info, state); + } + void OnDisconnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::Error& error) override { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error, state); + } + void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { + if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); } - // Auxiliary messages: - void On(unsigned sessionId, const NotificationData& data) override - { - if (m_parent->m_onNotification) m_parent->m_onNotification(sessionId, data); + void On(unsigned sessionId, Hermes::EVerticalState state, const Hermes::SupervisoryServiceDescriptionData& data) override { + if (m_parent->m_onSupervisoryServiceDescription) m_parent->m_onSupervisoryServiceDescription(data, state); + } + void On(unsigned sessionId, const Hermes::GetConfigurationData& data, const Hermes::ConnectionInfo& info) override { + if (m_parent->m_onGetConfiguration) m_parent->m_onGetConfiguration(data, info); + } + void On(unsigned sessionId, const Hermes::SetConfigurationData& data, const Hermes::ConnectionInfo& info) override { + if (m_parent->m_onSetConfiguration) m_parent->m_onSetConfiguration(data, info); } + void On(unsigned sessionId, const Hermes::SendWorkOrderInfoData& data) override { + if (m_parent->m_onSendWorkOrderInfo) m_parent->m_onSendWorkOrderInfo(data); + } + void On(unsigned sessionId, const Hermes::QueryHermesCapabilitiesData& data) override { + if (m_parent->m_onQueryHermesCapabilities) m_parent->m_onQueryHermesCapabilities(data); + } + void On(unsigned sessionId, const Hermes::NotificationData& data) override { + if (m_parent->m_onNotification) m_parent->m_onNotification(data); + } + void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); + } + }; - void On(unsigned sessionId, const CheckAliveData& data) override - { - if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(sessionId, data); + std::atomic m_isRunning; + std::thread m_networkThread; + std::unique_ptr m_callbackWrapper; + std::unique_ptr m_verticalService; + + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + TraceCallback m_onTrace; + + SupervisoryServiceDescriptionCallback m_onSupervisoryServiceDescription; + GetConfigurationCallback m_onGetConfiguration; + SetConfigurationCallback m_onSetConfiguration; + SendWorkOrderInfoCallback m_onSendWorkOrderInfo; + QueryHermesCapabilitiesCallback m_onQueryHermesCapabilities; + NotificationCallback m_onNotification; + CheckAliveCallback m_onCheckAlive; +}; + + +// ============================================================================== +// VerticalClient Wrapper (MES/Factory Cloud Client - Vertical) +// ============================================================================== +class VerticalClient { +public: + using ConnectedCallback = std::function; + using DisconnectedCallback = std::function; + using TraceCallback = std::function; + + using SupervisoryServiceDescriptionCallback = std::function; + using BoardArrivedCallback = std::function; + using BoardDepartedCallback = std::function; + using QueryWorkOrderInfoCallback = std::function; + using ReplyWorkOrderInfoCallback = std::function; + using CurrentConfigurationCallback = std::function; + using SendHermesCapabilitiesCallback = std::function; + + using NotificationCallback = std::function; + using CheckAliveCallback = std::function; + + VerticalClient() : m_isRunning(false) { + m_callbackWrapper = std::make_unique(this); + m_verticalClient = std::make_unique(*m_callbackWrapper); + } + + ~VerticalClient() { Stop(); } + + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } + + void RegisterSupervisoryServiceDescriptionCallback(SupervisoryServiceDescriptionCallback cb) { m_onSupervisoryServiceDescription = cb; } + void RegisterBoardArrivedCallback(BoardArrivedCallback cb) { m_onBoardArrived = cb; } + void RegisterBoardDepartedCallback(BoardDepartedCallback cb) { m_onBoardDeparted = cb; } + void RegisterQueryWorkOrderInfoCallback(QueryWorkOrderInfoCallback cb) { m_onQueryWorkOrderInfo = cb; } + void RegisterReplyWorkOrderInfoCallback(ReplyWorkOrderInfoCallback cb) { m_onReplyWorkOrderInfo = cb; } + void RegisterCurrentConfigurationCallback(CurrentConfigurationCallback cb) { m_onCurrentConfiguration = cb; } + void RegisterSendHermesCapabilitiesCallback(SendHermesCapabilitiesCallback cb) { m_onSendHermesCapabilities = cb; } + void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } + void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } + + void Enable(const VerticalClientSettings& settings) { + if (m_isRunning) return; + m_verticalClient->Enable(settings); + m_isRunning = true; + m_networkThread = std::thread([this]() { m_verticalClient->Run(); }); + } + + void Stop() { + if (m_isRunning) { + m_verticalClient->Stop(); + if (m_networkThread.joinable()) m_networkThread.join(); + m_isRunning = false; } + } - void On(unsigned sessionId, const CommandData& data) override - { - if (m_parent->m_onCommand) m_parent->m_onCommand(sessionId, data); + template + void Signal(unsigned sessionId, const T& data) { m_verticalClient->Signal(sessionId, data); } + +private: + class InternalCallbackWrapper : public Hermes::IVerticalClientCallback { + VerticalClient* m_parent; + public: + InternalCallbackWrapper(VerticalClient* parent) : m_parent(parent) {} + + void OnConnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::ConnectionInfo& info) override { + if (m_parent->m_onConnected) m_parent->m_onConnected(info, state); + } + void OnDisconnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::Error& error) override { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error, state); + } + void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { + if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); } - Upstream* m_parent; + void On(unsigned sessionId, Hermes::EVerticalState state, const Hermes::SupervisoryServiceDescriptionData& data) override { + if (m_parent->m_onSupervisoryServiceDescription) m_parent->m_onSupervisoryServiceDescription(data, state); + } + void On(unsigned sessionId, const Hermes::BoardArrivedData& data) override { + if (m_parent->m_onBoardArrived) m_parent->m_onBoardArrived(data); + } + void On(unsigned sessionId, const Hermes::BoardDepartedData& data) override { + if (m_parent->m_onBoardDeparted) m_parent->m_onBoardDeparted(data); + } + void On(unsigned sessionId, const Hermes::QueryWorkOrderInfoData& data) override { + if (m_parent->m_onQueryWorkOrderInfo) m_parent->m_onQueryWorkOrderInfo(data); + } + void On(unsigned sessionId, const Hermes::ReplyWorkOrderInfoData& data) override { + if (m_parent->m_onReplyWorkOrderInfo) m_parent->m_onReplyWorkOrderInfo(data); + } + void On(unsigned sessionId, const Hermes::CurrentConfigurationData& data) override { + if (m_parent->m_onCurrentConfiguration) m_parent->m_onCurrentConfiguration(data); + } + void On(unsigned sessionId, const Hermes::SendHermesCapabilitiesData& data) override { + if (m_parent->m_onSendHermesCapabilities) m_parent->m_onSendHermesCapabilities(data); + } + void On(unsigned sessionId, const Hermes::NotificationData& data) override { + if (m_parent->m_onNotification) m_parent->m_onNotification(data); + } + void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); + } }; - std::atomic m_isRunning; - std::thread m_networkThread; + std::atomic m_isRunning; + std::thread m_networkThread; std::unique_ptr m_callbackWrapper; - std::unique_ptr m_upstream; - - ConnectedCallback m_onConnected; - DisconnectedCallback m_onDisconnected; - StateChangeCallback m_onStateChange; - TraceCallback m_onTrace; - ServiceDescriptionCallback m_onServiceDescription; - BoardAvailableCallback m_onBoardAvailable; - RevokeBoardAvailableCallback m_onRevokeBoardAvailable; - TransportFinishedCallback m_onTransportFinished; - BoardForecastCallback m_onBoardForecast; - SendBoardInfoCallback m_onSendBoardInfo; - NotificationCallback m_onNotification; - CheckAliveCallback m_onCheckAlive; - CommandCallback m_onCommand; + std::unique_ptr m_verticalClient; + + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + TraceCallback m_onTrace; + + SupervisoryServiceDescriptionCallback m_onSupervisoryServiceDescription; + BoardArrivedCallback m_onBoardArrived; + BoardDepartedCallback m_onBoardDeparted; + QueryWorkOrderInfoCallback m_onQueryWorkOrderInfo; + ReplyWorkOrderInfoCallback m_onReplyWorkOrderInfo; + CurrentConfigurationCallback m_onCurrentConfiguration; + SendHermesCapabilitiesCallback m_onSendHermesCapabilities; + NotificationCallback m_onNotification; + CheckAliveCallback m_onCheckAlive; }; } // namespace Modern -} // namespace Hermes +} // namespace Hermes \ No newline at end of file diff --git a/src/include/HermesOptional.hpp b/src/include/HermesOptional.hpp index 1f04d8d..17d1122 100644 --- a/src/include/HermesOptional.hpp +++ b/src/include/HermesOptional.hpp @@ -16,20 +16,92 @@ limitations under the License. // Copyright (c) ASM Assembly Systems GmbH & Co. KG // -// FIX: The original file rolled its own Optional class to avoid C++11/Boost -// dependencies. Since this library now requires C++17 (enforced in CMakeLists), -// we use std::optional directly. This removes the custom implementation entirely -// and fixes the two-argument constructor mismatch that caused compile errors in -// HermesDataConversion.hpp (Optional{data.m_pData, data.m_size}). -// +// in order to avoid dependencies on boost and >= C++11, we roll out our own stripped down optional class #pragma once -#include -#include +#include +#include namespace Hermes { - // Drop-in alias — all code using Hermes::Optional continues to compile. template - using Optional = std::optional; + class Optional + { + public: + Optional() : m_hasValue(false) {} + + Optional(const T& value) : m_hasValue(true), m_value(value) {} + template + Optional(const U1& u1, const U2& u2) : m_hasValue(true), m_value(u1, u2) {} + + // for the time being, avoid C++11 and its variadic templates for better compatibility. Up to two params should do for Hermes ... + Optional& emplace() { m_value = T(); m_hasValue = true; return *this; } + template + Optional& emplace(const U& u) { m_value = T(u); m_hasValue = true; return *this; } + template + Optional& emplace(const U1& u1, const U2& u2) { m_value = T(u1, u2); m_hasValue = true; return *this; } + + Optional& operator=(const T& value) + { + m_hasValue = true; + m_value = value; + return *this; + } + + const T* operator->() const { assert(m_hasValue); return &m_value; } + T* operator->() { assert(m_hasValue); return &m_value; } + + const T& operator*() const { assert(m_hasValue); return m_value; } + T& operator*() { assert(m_hasValue); return m_value; } + + typedef bool Optional::*UnspecifiedBoolType; + operator UnspecifiedBoolType() const { return m_hasValue ? &Optional::m_hasValue : 0; } + bool operator!() const { return !m_hasValue; } + bool has_value() const { return m_hasValue; } + + const T& value_or(const T& other) const { return m_hasValue ? m_value : other; } + T& value_or(T& other) { return m_hasValue ? m_value : other; } + + void swap(Optional& rhs) + { + using std::swap; + swap(m_hasValue, rhs.m_hasValue); + swap(m_value, rhs.m_value); + } + friend void swap(Optional& lhs, Optional& rhs) { lhs.swap(rhs); } + + void reset() + { + m_hasValue = false; + m_value = T(); + } + + friend bool operator==(const Optional& lhs, const Optional& rhs) + { + if (lhs.m_hasValue && rhs.m_hasValue) + return lhs.m_value == rhs.m_value; + + return lhs.m_hasValue == rhs.m_hasValue; + } + friend bool operator!=(const Optional& lhs, const Optional& rhs) { return !operator==(lhs, rhs); } + + template + friend S& operator<<(S& s, const Optional& o) + { + if (o.m_hasValue) + { + s << o.m_value; + } + else + { + s << ""; + } + return s; + } + + private: + + bool m_hasValue; + T m_value; + }; } diff --git a/src/include/HermesStringView.h b/src/include/HermesStringView.h index d9fa85e..fc19c8f 100644 --- a/src/include/HermesStringView.h +++ b/src/include/HermesStringView.h @@ -1,26 +1,29 @@ // Copyright (c) ASM Assembly Systems GmbH & Co. KG // -// C interface string view — a non-owning reference to a character sequence. -// This header is used by both C and C++ translation units. +// C interface helper for Hermes // -#ifndef HERMES_STRING_VIEW_H -#define HERMES_STRING_VIEW_H +#ifndef HERMESSTRINGVIEW_H +#define HERMESSTRINGVIEW_H #include -#include /* FIX: required for size_t */ +#include #ifdef __cplusplus extern "C" { #endif -struct HermesStringView -{ - const char* m_pData; - size_t m_size; /* FIX: field was missing in previous version */ -}; + // Not part of The Hermes Standard, but used extensively: a non-owning string type, in the spirit of std::string_view. + // This relieves us from the need to terminate all strings with \0. + // Note that all the C interface structures are non-owning and need to be backed up by actual storage. + struct HermesStringView + { + const char* m_pData; // if nullptr then we have no string at all - not even an empty string + size_t m_size; + }; #ifdef __cplusplus } #endif -#endif /* HERMES_STRING_VIEW_H */ +#endif //HERMESSTRINGVIEW_H + diff --git a/src/include/HermesStringView.hpp b/src/include/HermesStringView.hpp index dadafdd..98f05d8 100644 --- a/src/include/HermesStringView.hpp +++ b/src/include/HermesStringView.hpp @@ -15,18 +15,111 @@ limitations under the License. ************************************************************************/ // Copyright (c) ASM Assembly Systems GmbH & Co. KG -// -// FIX: The original file implemented a custom StringView class to avoid -// std::string_view which requires C++17. Since this library now requires -// C++17 (enforced in CMakeLists), we use std::string_view directly. -// All code using Hermes::StringView continues to compile unchanged. -// #pragma once -#include +#include +#include +#include +#include + +// while we have not got std::string_view at our disposal, we make our own: namespace Hermes { - // Drop-in alias — all code using Hermes::StringView continues to compile. - using StringView = std::string_view; -} + class StringView + { + public: + constexpr StringView() = default; + StringView(const char* pStr) : m_pData(pStr), m_size(std::strlen(pStr)) {} + constexpr StringView(const char* pData, std::size_t size) : m_pData(pData), m_size(size) {} + StringView(const std::string& str) : m_pData(str.data()), m_size(str.size()) {} + StringView& operator=(const std::string& str) + { + m_pData = str.data(); + m_size = str.size(); + return *this; + } + StringView& operator=(const char* pStr) + { + m_pData = pStr; + m_size = ::strlen(pStr); + return *this; + } + + operator std::string() const { return std::string(m_pData, m_size); } + + constexpr const char* data() const { return m_pData; } + constexpr std::size_t size() const { return m_size; } + constexpr std::size_t length() const { return m_size; } + constexpr bool empty() const { return m_size == 0U; } + constexpr StringView substr(std::size_t pos, std::size_t count = std::string::npos) const { return{m_pData + pos, std::min(count, m_size - pos)}; } + + std::size_t find(char c, size_t pos = 0U) + { + if (pos >= m_size) + return std::string::npos; + + auto* pFound = Traits_::find(m_pData + pos, m_size - pos, c); + return pFound ? pFound - m_pData : std::string::npos; + } + + std::size_t find(StringView v) const + { + // empty string always matches: + if (v.empty()) + return 0U; + + // do not bother if size is too large: + if (m_size < v.m_size) + return std::string::npos; + + const char* pMatch; + const char* pLast = m_pData + m_size - v.m_size + 1; + for (auto* p = m_pData; + (pMatch = Traits_::find(p, pLast - p, *v.m_pData)) != nullptr; + p = pMatch + 1) + { + if (Traits_::compare(pMatch, v.m_pData, v.m_size) == 0) + return pMatch - m_pData; + } + return std::string::npos; + } + + + int compare(StringView rhs) + { + auto minSize = m_size < rhs.m_size ? m_size : rhs.m_size; + auto cmp = std::char_traits::compare(m_pData, rhs.m_pData, minSize); + + if (cmp) + return cmp; + if (m_size < rhs.m_size) + return -1; + if (rhs.m_size < m_size) + return 1; + return 0; + } + int compare(std::size_t pos, std::size_t count, StringView rhs) { return substr(pos, count).compare(rhs); } + + friend std::ostream& operator<<(std::ostream& os, StringView sv) + { + os.write(sv.m_pData, sv.m_size); + return os; + } + + private: + const char* m_pData = nullptr; + std::size_t m_size = 0U; + using Traits_ = std::char_traits; + }; + + inline bool operator==(StringView lhs, StringView rhs) + { + return lhs.compare(rhs) == 0; + } + + inline bool operator!=(StringView lhs, StringView rhs) + { + return !operator==(lhs, rhs); + } +} \ No newline at end of file From 2bec06d1bc37fd6e05f4921919aeb91529c3be50 Mon Sep 17 00:00:00 2001 From: sahil agarwal Date: Fri, 27 Mar 2026 14:44:53 +0530 Subject: [PATCH 13/14] updated examples --- README.md | 232 +++++++++++++++++++------------- examples/interactive_sender.cpp | 75 ----------- examples/machine_b_pnp.cpp | 68 ---------- examples/machine_c_oven.cpp | 41 ------ examples/rpi1_upstream.cpp | 220 +++++++++++++----------------- examples/rpi2_downstream.cpp | 224 +++++++++++++----------------- 6 files changed, 328 insertions(+), 532 deletions(-) delete mode 100644 examples/interactive_sender.cpp delete mode 100644 examples/machine_b_pnp.cpp delete mode 100644 examples/machine_c_oven.cpp diff --git a/README.md b/README.md index 2a90fba..26ea7ea 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,141 @@ -Hermes C++ Library (lib_cpp) -The Hermes Standard (IPC-HERMES-9852) is the modern, non-proprietary TCP/IP and XML-based successor to the legacy SMEMA standard. It enables vendor-independent machine-to-machine communication in SMT assembly lines. This repository provides the official, high-performance C++ implementation of the protocol. +# Hermes C++ Library — Complete Reference + +**Protocol:** The Hermes Standard — vendor-independent machine-to-machine communication for SMT assembly lines +**License:** Apache 2.0 +**Requires:** C++17, Boost 1.66+, CMake 3.15+ + +--- + +## Table of Contents + +1. [What is Hermes](#1-what-is-hermes) +2. [Architecture overview](#2-architecture-overview) +3. [Building the library](#3-building-the-library) +4. [Header map — what to include](#4-header-map) +5. [Core types reference](#5-core-types-reference) +6. [Modern C++ API — HermesModern.hpp](#6-modern-c-api) +7. [Low-level C++ API — Hermes.hpp](#7-low-level-c-api) +8. [Serialization API — HermesSerialization.hpp](#8-serialization-api) +9. [Configuration service](#9-configuration-service) +10. [Vertical interface](#10-vertical-interface) +11. [Complete examples](#11-complete-examples) +12. [Bugs fixed in this version](#12-bugs-fixed) + +--- + +## 1. What is Hermes + +Hermes is an open TCP/IP + XML protocol that connects machines in an electronics assembly line. Each machine has an **Upstream** port (faces the previous machine) and a **Downstream** port (faces the next machine). PCB boards flow from Upstream to Downstream along the lane. + +``` +[Machine A] --downstream:50100--> [Machine B] --downstream:50101--> [Machine C] + <--upstream:50100--- <--upstream:50101--- +``` + +The Downstream machine **listens** on a port. The Upstream machine **connects** to it. So: + +- `Hermes::Downstream` / `Modern::Downstream` — **server role**, listens for incoming connections +- `Hermes::Upstream` / `Modern::Upstream` — **client role**, connects to the downstream machine + +--- + +## 2. Architecture overview + +``` +Your application + | + |--- HermesModern.hpp (std::function callbacks — recommended) + |--- Hermes.hpp (virtual interface callbacks — advanced) + |--- HermesSerialization (XML serialize/deserialize) + | + +---> Hermes C API (Hermes.h / HermesData.h) + | compiled into libhermes.so + | + +---> Boost.Asio (networking) + +---> pugixml (XML parsing) +``` + +### Message flow — horizontal (machine to machine) + +``` +Downstream machine Upstream machine +(Modern::Downstream) (Modern::Upstream) + | | + |<-- TCP connect -------------------- | + | | + |<-- ServiceDescription ------------- | (Upstream identifies itself) + |--> ServiceDescription -----------> | (Downstream identifies itself) + | | + |<-- MachineReady ------------------- | (Upstream ready to receive) + |--> BoardAvailable --------------> | (Downstream has a board) + | | + |<-- StartTransport ----------------- | (Upstream says: send it) + | [board physically moves] | + |--> TransportFinished -----------> | (Downstream confirms done) + | | + |<-- StopTransport ---------------- | (Upstream confirms received) +``` + +### Who receives what + +| You create | You listen for (receive) | You send | +|-------------------|-------------------------------------------------------------------|-------------------------------------------------------| +| `Modern::Downstream` | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | +| `Modern::Upstream` | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | + +--- + +## 3. Building the library + +### Prerequisites + +```bash +# Debian / Ubuntu / Raspberry Pi OS +sudo apt install -y g++ cmake libboost-all-dev + +# macOS +brew install cmake boost + +# Windows +# Install Boost via vcpkg: vcpkg install boost +``` + +### Build + +```bash +git clone https://github.com/hermes-org/lib_cpp.git +cd lib_cpp +mkdir build && cd build +cmake .. +make -j4 +``` + +This produces `build/src/Hermes/libhermes.so` (Linux/macOS) or `hermes.dll` (Windows). + +### Compile your application + +```bash +g++ -std=c++17 -o myapp myapp.cpp \ + -I./src/include \ + -L./build/src/Hermes -lhermes \ + -lboost_system -lpthread +#change the commands according to your OS -1. Core Repository Map -To navigate the library effectively, refer to the following directory structure: - -src/include/: The Public API. This directory contains the headers required to integrate Hermes into your application. - -Hermes.hpp: The primary Object-Oriented C++ wrapper. - -Hermes.h: The low-level C-style API. - -src/Hermes/: The Implementation. Contains the core logic, including the ASIO networking stack, XML serialization, and the standard-compliant state machines. - -test/BoostTestHermes/: Verification Suite. Contains comprehensive unit and integration tests. This is the source of truth for protocol-compliant behavior. - -References/: External Headers. Contains local versions of pugixml and boost headers necessary for compilation in restricted environments. - -2. Technical Requirements -Dependencies -C++ Standard: C++17 or higher. - -Networking: Boost.ASIO (v1.66 through v1.78 recommended). - -Note: Versions 1.87+ require the -DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT flag. - -XML Processing: pugixml (included in References/). - -Platform Independence -The library is designed to be fully platform-independent. It supports: - -Windows: MSVC (2017+) and MinGW-w64. - -Linux: GCC (7+) and Clang. - -Build System: The project utilizes CMake (3.15+) to generate native build files (Visual Studio Solutions or Makefiles) for your specific platform. - -3. Unified Build Process (CMake) -To build the library on any platform, use the following standard workflow: - -Generate: mkdir build && cd build && cmake .. - -Build: cmake --build . --config Release - -Outputs: - -hermes.dll / hermes.lib (Windows) - -libhermes.so (Linux) - -4. API Architecture & Design -The Callback Model -The C++ API follows a strict Interface-Based Callback pattern. - -Users must implement classes inheriting from IDownstreamCallback (Receivers) or IUpstreamCallback (Senders). - -All pure virtual methods in these interfaces must be overridden to handle protocol events (Connection, Disconnection, Board Available, Machine Ready, etc.). - -Execution Model -Blocking Event Loop: The core processing occurs within the Run() method. - -Threading: Because Run() blocks the calling thread to process network I/O, it must be executed in a dedicated background thread to maintain application responsiveness. - -State Management: The library handles all internal state transitions (e.g., Not Connected -> Service Description -> Not Ready -> Ready) automatically based on the Enable() configuration. - -5. Supported Communication Channels -The library implements the four primary channels defined by IPC-HERMES-9852: - -Upstream: Machine-to-Machine (Sending). - -Downstream: Machine-to-Machine (Receiving). - -Configuration: Exchange of machine capabilities and line settings. - -Vertical: Communication with factory-level MES/ERP systems. - -6. Deployment Topologies -The library supports standard IPv4 networking across various hardware setups: - -Point-to-Point: Direct Ethernet connection between two machines. - -Switched Fabric: Deployment via factory-wide network switches. This is the preferred method for modern "Smart Factory" environments, as it allows a single network interface to handle both horizontal (machine) and vertical (MES) data simultaneously. - -7. Quality Assurance -All protocol features are verified against the BoostTestHermes suite. - -Unit Tests: Verify individual XML serialization and data structures. - -Integration Tests: Simulate full handshakes between virtual Upstream and Downstream sessions. - -Automation: Compatible with ctest for continuous integration workflows. - -License: Copyright (c) ASM Assembly Systems GmbH & Co. KG. Licensed under the Apache License, Version 2.0. See COPYRIGHT.txt for details. \ No newline at end of file +# Run (Linux — tell the linker where to find the .so) +LD_LIBRARY_PATH=./build/src/Hermes ./myapp +``` + +--- + +## 4. Header map + +| Header | What it gives you | When to include it | +|--------|------------------|--------------------| +| `HermesModern.hpp` | `Modern::Downstream`, `Modern::Upstream` with std::function callbacks | **Start here. Recommended for all new code.** | +| `Hermes.hpp` | `Hermes::Downstream`, `Hermes::Upstream`, virtual callback interfaces | When you need session IDs, raw XML, or fine-grained control | +| `HermesData.hpp` | All C++ data structs, enums, settings structs | Included automatically by the above | +| `HermesSerialization.hpp` | `ToXml()`, `FromXml()` | When you need to inspect or log raw XML messages | +| `HermesOptional.hpp` | `Hermes::Optional` = `std::optional` | Included automatically | +| `HermesStringView.hpp` | `Hermes::StringView` = `std::string_view` | Included automatically | + +--- \ No newline at end of file diff --git a/examples/interactive_sender.cpp b/examples/interactive_sender.cpp deleted file mode 100644 index 5e48413..0000000 --- a/examples/interactive_sender.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include -#include -#include "HermesModern.hpp" - -void PrintMenu() { - std::cout << "\n=========================================\n"; - std::cout << " INTERACTIVE HERMES SENDER CONSOLE\n"; - std::cout << "=========================================\n"; - std::cout << " Type a command and press Enter:\n"; - std::cout << " service -> Send ServiceDescription\n"; - std::cout << " board -> Send BoardAvailable (e.g., board PCB-123)\n"; - std::cout << " start -> Send TransportFinished (e.g., start PCB-123)\n"; - std::cout << " exit -> Shut down and close\n"; - std::cout << "=========================================\n"; -} - -int main() { - // Senders face DOWNSTREAM and act as TCP Servers - Hermes::Modern::Downstream sender(1); - - sender.RegisterConnectedCallback([&](const Hermes::ConnectionInfo& info) { - std::cout << "\n[NETWORK] Connected to Receiver at " << info.m_address << "!\n> "; - }); - - sender.RegisterMachineReadyCallback([&](const Hermes::MachineReadyData& data) { - std::cout << "\n[RECEIVER SAYS] I am MachineReady! Send me a board.\n> "; - }); - - sender.RegisterStartTransportCallback([&](const Hermes::StartTransportData& data) { - std::cout << "\n[RECEIVER SAYS] StartTransport for " << data.m_boardId << "! My conveyors are running.\n> "; - }); - - sender.RegisterStopTransportCallback([&](const Hermes::StopTransportData& data) { - std::cout << "\n[RECEIVER SAYS] StopTransport for " << data.m_boardId << ". I have the board completely.\n> "; - }); - - std::cout << "Opening Server on port 50101...\n"; - sender.Enable(Hermes::DownstreamSettings("Interactive_Sender", 50101)); - - PrintMenu(); - std::string command; - - while (true) { - std::cout << "> "; - std::cin >> command; - - if (command == "exit") { - break; - } - else if (command == "service") { - sender.Signal(1, Hermes::ServiceDescriptionData("Interactive_Sender", 1)); - std::cout << "[SENT] ServiceDescriptionData\n"; - } - else if (command == "board") { - std::string boardId; - std::cin >> boardId; - sender.Signal(1, Hermes::BoardAvailableData(boardId, "Interactive_Sender", Hermes::EBoardQuality::eGOOD, Hermes::EFlippedBoard::eTOP_SIDE_IS_UP)); - std::cout << "[SENT] BoardAvailableData for " << boardId << "\n"; - } - else if (command == "start") { - std::string boardId; - std::cin >> boardId; - sender.Signal(1, Hermes::TransportFinishedData(Hermes::ETransferState::eCOMPLETE, boardId)); - std::cout << "[SENT] TransportFinishedData for " << boardId << "\n"; - } - else { - std::cout << "[ERROR] Unknown command.\n"; - std::cin.clear(); - std::cin.ignore(10000, '\n'); - } - } - - sender.Stop(); - return 0; -} diff --git a/examples/machine_b_pnp.cpp b/examples/machine_b_pnp.cpp deleted file mode 100644 index 995c848..0000000 --- a/examples/machine_b_pnp.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include -#include -#include -#include -#include "HermesModern.hpp" - -int main() { - std::cout << "========================================\n"; - std::cout << " [MACHINE B] - PICK & PLACE\n"; - std::cout << " Connecting to 50101 (A) | Listening on 50102 (C)\n"; - std::cout << "========================================\n\n"; - - // Receiver connects UPSTREAM to A. Sender listens DOWNSTREAM for C. - Hermes::Modern::Upstream receiver(1); - Hermes::Modern::Downstream sender(1); - - // --- RECEIVING FROM MACHINE A (Upstream) --- - receiver.RegisterServiceDescriptionCallback([&](const Hermes::ServiceDescriptionData& data) { - std::cout << "[B] Upstream " << data.m_machineId << " connected. I am ready.\n"; - receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); - }); - - receiver.RegisterBoardAvailableCallback([&](const Hermes::BoardAvailableData& board) { - std::cout << "[B] Board " << board.m_boardId << " waiting at upstream edge. Pulling...\n"; - receiver.Signal(1, Hermes::StartTransportData(board.m_boardId)); - }); - - receiver.RegisterTransportFinishedCallback([&](const Hermes::TransportFinishedData& data) { - receiver.Signal(1, Hermes::StopTransportData(Hermes::ETransferState::eCOMPLETE, data.m_boardId)); - std::cout << "[B] Board " << data.m_boardId << " received. Placing components...\n"; - - std::this_thread::sleep_for(std::chrono::seconds(3)); - std::cout << "[B] Placement done! Passing to Machine C...\n"; - - sender.Signal(1, Hermes::BoardAvailableData(data.m_boardId, "Machine_B_PnP", Hermes::EBoardQuality::eGOOD, Hermes::EFlippedBoard::eTOP_SIDE_IS_UP)); - }); - - // --- SENDING TO MACHINE C (Downstream) --- - sender.RegisterConnectedCallback([&](const Hermes::ConnectionInfo& info) { - std::cout << "[B] Connected to Downstream at " << info.m_address << "\n"; - sender.Signal(1, Hermes::ServiceDescriptionData("Machine_B_PnP", 1)); - }); - - sender.RegisterMachineReadyCallback([&](const Hermes::MachineReadyData&) { - std::cout << "[B] Machine C is ready for boards.\n"; - }); - - sender.RegisterStartTransportCallback([&](const Hermes::StartTransportData& data) { - std::cout << "[B] Machine C is pulling. Running exit conveyors...\n"; - std::this_thread::sleep_for(std::chrono::seconds(1)); - - sender.Signal(1, Hermes::TransportFinishedData(Hermes::ETransferState::eCOMPLETE, data.m_boardId)); - std::cout << "[B] Board successfully handed off to Machine C.\n\n"; - - receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); - }); - - // Start Server first, then connect Client - sender.Enable(Hermes::DownstreamSettings("Machine_B_PnP", 50102)); - std::this_thread::sleep_for(std::chrono::milliseconds(500)); - receiver.Enable(Hermes::UpstreamSettings("Machine_B_PnP", "127.0.0.1", 50101)); - - std::cout << "Press Enter to shut down Machine B...\n\n"; - std::cin.get(); - receiver.Stop(); - sender.Stop(); - return 0; -} \ No newline at end of file diff --git a/examples/machine_c_oven.cpp b/examples/machine_c_oven.cpp deleted file mode 100644 index 8dba590..0000000 --- a/examples/machine_c_oven.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include -#include -#include "HermesModern.hpp" - -int main() { - std::cout << "========================================\n"; - std::cout << " [MACHINE C] - REFLOW OVEN (Receiver)\n"; - std::cout << " Connecting to Port 50102...\n"; - std::cout << "========================================\n\n"; - - // Receivers face UPSTREAM to connect to the previous machine - Hermes::Modern::Upstream receiver(1); - - receiver.RegisterServiceDescriptionCallback([&](const Hermes::ServiceDescriptionData& data) { - std::cout << "[C] Connected to " << data.m_machineId << ". Ready for boards.\n"; - receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); - }); - - receiver.RegisterBoardAvailableCallback([&](const Hermes::BoardAvailableData& board) { - std::cout << "[C] Board detected at entrance: " << board.m_boardId << "\n"; - std::cout << "[C] Starting conveyors to pull it in...\n"; - receiver.Signal(1, Hermes::StartTransportData(board.m_boardId)); - }); - - receiver.RegisterTransportFinishedCallback([&](const Hermes::TransportFinishedData& data) { - receiver.Signal(1, Hermes::StopTransportData(Hermes::ETransferState::eCOMPLETE, data.m_boardId)); - std::cout << "[C] Board " << data.m_boardId << " is fully inside!\n"; - std::cout << "[C] Baking... Handover cycle complete.\n\n"; - - receiver.Signal(1, Hermes::MachineReadyData(Hermes::EBoardQuality::eANY)); - }); - - // Client connects to the Sender - Hermes::UpstreamSettings settings("Machine_C_Oven", "127.0.0.1", 50102); - receiver.Enable(settings); - - std::cout << "Press Enter to shut down Machine C...\n\n"; - std::cin.get(); - receiver.Stop(); - return 0; -} \ No newline at end of file diff --git a/examples/rpi1_upstream.cpp b/examples/rpi1_upstream.cpp index 960514d..95c6110 100644 --- a/examples/rpi1_upstream.cpp +++ b/examples/rpi1_upstream.cpp @@ -1,155 +1,121 @@ -// ============================================================================= -// rpi1_upstream.cpp — RPi1: Upstream machine -// -// Build: -// g++ -std=c++17 -o rpi1_upstream rpi1_upstream.cpp \ -// -I/path/to/hermes/src/include \ -// -L/path/to/hermes/build -lhermes \ -// -lboost_system -lpthread -// -// Run: -// LD_LIBRARY_PATH=/path/to/hermes/build ./rpi1_upstream -// -// What it does: -// - Connects to RPi2 (Downstream) on port 50100 -// - Sends ServiceDescription to identify itself -// - Waits for ServiceDescription back from RPi2 -// - Prints connection state changes to console -// ============================================================================= - #include "Hermes.hpp" #include -#include #include -#include #include #include - -// ----------------------------------------------------------------------- -// EDIT THIS: set RPi2's IP address here -// On RPi2, run: hostname -I -// ----------------------------------------------------------------------- -static const std::string RPI2_IP = "192.168.1.102"; // <-- CHANGE THIS -static const uint16_t HERMES_PORT = 50100; -static const std::string THIS_MACHINE_ID = "RPi1-Upstream"; +#include +#include static std::atomic g_running{true}; +static unsigned g_activeSession = 0; +static std::atomic g_currentStateInt{0}; + +bool kbhit() { + struct timeval tv = {0L, 0L}; + fd_set fds; + FD_ZERO(&fds); + FD_SET(0, &fds); + return select(1, &fds, NULL, NULL, &tv) > 0; +} -void signalHandler(int) { g_running = false; } +struct UpstreamCallback : Hermes::IUpstreamCallback { + Hermes::Upstream* m_pUpstream = nullptr; -// ----------------------------------------------------------------------- -// Upstream callback — receives messages FROM the Downstream (RPi2) -// ----------------------------------------------------------------------- -struct UpstreamCallback : Hermes::IUpstreamCallback -{ - void OnConnected(unsigned sessionId, Hermes::EState state, - const Hermes::ConnectionInfo& info) override - { - std::cout << "[RPi1] Connected to RPi2" - << " address=" << info.m_address - << " port=" << info.m_port - << " session=" << sessionId - << "\n"; + void OnConnected(unsigned sessionId, Hermes::EState, const Hermes::ConnectionInfo&) override { + g_activeSession = sessionId; + m_pUpstream->Post([this, sessionId]() { + Hermes::ServiceDescriptionData data; + data.m_machineId = "RPi1_Upstream"; + m_pUpstream->Signal(sessionId, data); + }); } - // RPi2 sends its ServiceDescription — we receive it here - void On(unsigned sessionId, Hermes::EState state, - const Hermes::ServiceDescriptionData& data) override - { - std::cout << "[RPi1] Received ServiceDescription from RPi2" - << " machineId=" << data.m_machineId - << " laneId=" << data.m_laneId - << "\n"; - std::cout << "[RPi1] ** Hermes connection established successfully **\n"; + void On(unsigned sessionId, Hermes::EState, const Hermes::BoardAvailableData& b) override { + std::cout << "\n[RPi1] BOARD DETECTED: " << b.m_boardId << "\n"; + m_pUpstream->Post([this, sessionId, b]() { + Hermes::StartTransportData st; + st.m_boardId = b.m_boardId; + m_pUpstream->Signal(sessionId, st); + std::cout << "[RPi1] Auto-Sent: StartTransport\n"; + }); } - // These are required by the interface but not relevant for this demo - void On(unsigned, Hermes::EState, const Hermes::BoardAvailableData&) override {} - void On(unsigned, Hermes::EState, const Hermes::RevokeBoardAvailableData&) override {} - void On(unsigned, Hermes::EState, const Hermes::TransportFinishedData&) override {} - - void On(unsigned sessionId, const Hermes::NotificationData& data) override - { - std::cout << "[RPi1] Notification from RPi2: " << data.m_description << "\n"; + void On(unsigned sessionId, Hermes::EState, const Hermes::TransportFinishedData& tf) override { + std::cout << "[RPi1] RPi2 Transfer Complete. Executing Loop Reset...\n"; + m_pUpstream->Post([this, sessionId, tf]() { + Hermes::StopTransportData stop; + stop.m_boardId = tf.m_boardId; + stop.m_transferState = Hermes::ETransferState::eCOMPLETE; + m_pUpstream->Signal(sessionId, stop); + std::cout << "[RPi1] Sent StopTransport. System Ready for Next Cycle.\n"; + }); } - void On(unsigned, const Hermes::CheckAliveData&) override {} - - void On(unsigned sessionId, const Hermes::CommandData&) override {} - - void OnState(unsigned sessionId, Hermes::EState state) override - { - std::cout << "[RPi1] State changed: " << static_cast(state) << "\n"; + void OnState(unsigned, Hermes::EState s) override { + g_currentStateInt = static_cast(s); + std::cout << "[RPi1] State Transition: " << g_currentStateInt << "\n"; } - void OnDisconnected(unsigned sessionId, Hermes::EState state, - const Hermes::Error& error) override - { - std::cout << "[RPi1] Disconnected from RPi2"; - if (error) - std::cout << " reason=" << error.m_text; - std::cout << "\n"; + void OnDisconnected(unsigned, Hermes::EState, const Hermes::Error& e) override { + g_activeSession = 0; std::cout << "[RPi1] Disconnected: " << e.m_text << "\n"; } - void OnTrace(unsigned, Hermes::ETraceType type, Hermes::StringView trace) override - { - // Uncomment to see full protocol trace: - // std::cout << "[RPi1][TRACE] " << std::string(trace) << "\n"; - } + void On(unsigned, Hermes::EState, const Hermes::ServiceDescriptionData&) override {} + void On(unsigned, Hermes::EState, const Hermes::RevokeBoardAvailableData&) override {} + void On(unsigned, const Hermes::NotificationData&) override {} + void On(unsigned, const Hermes::CheckAliveData&) override {} + void On(unsigned, const Hermes::CommandData&) override {} + void OnTrace(unsigned, Hermes::ETraceType, Hermes::StringView) override {} }; -int main() -{ - std::signal(SIGINT, signalHandler); - std::signal(SIGTERM, signalHandler); - - std::cout << "[RPi1] Starting Hermes Upstream\n"; - std::cout << "[RPi1] Connecting to RPi2 at " << RPI2_IP - << ":" << HERMES_PORT << "\n"; +void set_raw_mode(bool enable) { + static struct termios oldt, newt; + if (enable) { + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + } else { + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + } +} +int main() { + std::signal(SIGINT, [](int) { g_running = false; }); + set_raw_mode(true); + UpstreamCallback callback; - Hermes::Upstream upstream(1, callback); // lane 1 - - // Settings: who we are, and who we connect to + Hermes::Upstream upstream(1, callback); + callback.m_pUpstream = &upstream; + Hermes::UpstreamSettings settings; - settings.m_machineId = THIS_MACHINE_ID; - settings.m_hostAddress = RPI2_IP; - settings.m_port = HERMES_PORT; - settings.m_checkAlivePeriodInSeconds = 60.0; - settings.m_reconnectWaitTimeInSeconds = 5.0; - - // Enable runs the TCP connection + Hermes handshake in background + settings.m_machineId = "RPi1_Upstream"; + settings.m_hostAddress = "10.0.0.2"; + settings.m_port = 50100; + upstream.Enable(settings); - - // Run the Hermes event loop in a thread - std::thread networkThread([&upstream]() { - upstream.Run(); - }); - - // After connection, send our ServiceDescription to RPi2 - // We post it onto the Hermes thread after a short delay to let - // the connection establish first - std::this_thread::sleep_for(std::chrono::seconds(2)); - - upstream.Post([&upstream]() { - Hermes::ServiceDescriptionData desc; - desc.m_machineId = THIS_MACHINE_ID; - desc.m_laneId = 1; - // session 0 = send to whatever session is currently active - // The library fills in the real session id internally via Post - std::cout << "[RPi1] Sending ServiceDescription to RPi2\n"; - }); - - std::cout << "[RPi1] Running. Press Ctrl+C to stop.\n"; - - while (g_running) - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - - std::cout << "[RPi1] Shutting down...\n"; + std::thread netThread([&]() { upstream.Run(); }); + + std::cout << "Upstream Node Active. Tap 'r' to signal MachineReady.\n"; + + while (g_running) { + if (kbhit()) { + char c = getchar(); + if (g_activeSession != 0) { + if (c == 'r' || c == 'R') { + std::cout << "[RPi1] Manual Override: Sending Ready...\n"; + upstream.Post([&upstream]() { + Hermes::MachineReadyData mr; + upstream.Signal(g_activeSession, mr); + }); + } else if (c == 'q' || c == 'Q') g_running = false; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + set_raw_mode(false); upstream.Stop(); - if (networkThread.joinable()) - networkThread.join(); - - std::cout << "[RPi1] Done.\n"; + if (netThread.joinable()) netThread.join(); return 0; } diff --git a/examples/rpi2_downstream.cpp b/examples/rpi2_downstream.cpp index 0547f36..0ffe777 100644 --- a/examples/rpi2_downstream.cpp +++ b/examples/rpi2_downstream.cpp @@ -1,157 +1,123 @@ -// ============================================================================= -// rpi2_downstream.cpp — RPi2: Downstream machine -// -// Build: -// g++ -std=c++17 -o rpi2_downstream rpi2_downstream.cpp \ -// -I/path/to/hermes/src/include \ -// -L/path/to/hermes/build -lhermes \ -// -lboost_system -lpthread -// -// Run: -// LD_LIBRARY_PATH=/path/to/hermes/build ./rpi2_downstream -// -// What it does: -// - Listens on port 50100 for an incoming connection from RPi1 -// - Receives ServiceDescription from RPi1 -// - Sends ServiceDescription back to RPi1 -// - Prints connection state changes to console -// ============================================================================= - #include "Hermes.hpp" #include -#include #include -#include #include #include +#include +#include +#include -static const uint16_t HERMES_PORT = 50100; -static const std::string THIS_MACHINE_ID = "RPi2-Downstream"; - -static std::atomic g_running{true}; -static unsigned g_activeSession{0}; +static std::atomic g_running{true}; +static unsigned g_activeSession = 0; +static std::atomic g_currentStateInt{0}; +static int g_counter = 100; +static std::string g_currentBoardId = ""; + +void set_raw_mode(bool enable) { + static struct termios oldt, newt; + if (enable) { + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + } else { + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + } +} -// Forward declare so callback can reference it -Hermes::Downstream* g_downstream = nullptr; +bool kbhit() { + struct timeval tv = {0L, 0L}; + fd_set fds; + FD_ZERO(&fds); + FD_SET(0, &fds); + return select(1, &fds, NULL, NULL, &tv) > 0; +} -void signalHandler(int) { g_running = false; } +struct DownstreamCallback : Hermes::IDownstreamCallback { + Hermes::Downstream* m_pDownstream = nullptr; -// ----------------------------------------------------------------------- -// Downstream callback — receives messages FROM the Upstream (RPi1) -// ----------------------------------------------------------------------- -struct DownstreamCallback : Hermes::IDownstreamCallback -{ - void OnConnected(unsigned sessionId, Hermes::EState state, - const Hermes::ConnectionInfo& info) override - { + void OnConnected(unsigned sessionId, Hermes::EState, const Hermes::ConnectionInfo&) override { g_activeSession = sessionId; - std::cout << "[RPi2] RPi1 connected" - << " address=" << info.m_address - << " port=" << info.m_port - << " session=" << sessionId - << "\n"; + std::cout << "\n[RPi2] CONNECTION ESTABLISHED.\n"; } - // RPi1 sends its ServiceDescription — we receive it here - void On(unsigned sessionId, Hermes::EState state, - const Hermes::ServiceDescriptionData& data) override - { - std::cout << "[RPi2] Received ServiceDescription from RPi1" - << " machineId=" << data.m_machineId - << " laneId=" << data.m_laneId - << "\n"; - - // Respond with our own ServiceDescription - if (g_downstream) - { - g_downstream->Post([sessionId]() { - Hermes::ServiceDescriptionData reply; - reply.m_machineId = THIS_MACHINE_ID; - reply.m_laneId = 1; - - std::cout << "[RPi2] Sending ServiceDescription back to RPi1\n"; - g_downstream->Signal(sessionId, reply); - std::cout << "[RPi2] ** Hermes connection established successfully **\n"; - }); - } + void On(unsigned sessionId, Hermes::EState, const Hermes::ServiceDescriptionData&) override { + m_pDownstream->Post([this, sessionId]() { + Hermes::ServiceDescriptionData reply; + reply.m_machineId = "RPi2_Downstream"; + m_pDownstream->Signal(sessionId, reply); + }); } - // These are required by the interface — not used in this demo - void On(unsigned, Hermes::EState, const Hermes::MachineReadyData&) override {} - void On(unsigned, Hermes::EState, const Hermes::RevokeMachineReadyData&) override {} - void On(unsigned, Hermes::EState, const Hermes::StartTransportData&) override {} - void On(unsigned, Hermes::EState, const Hermes::StopTransportData&) override {} - - void On(unsigned sessionId, const Hermes::NotificationData& data) override - { - std::cout << "[RPi2] Notification from RPi1: " << data.m_description << "\n"; + void OnState(unsigned, Hermes::EState s) override { + g_currentStateInt = static_cast(s); + std::cout << "[RPi2] State Transition: " << g_currentStateInt << "\n"; } - void On(unsigned, const Hermes::CheckAliveData&) override {} - - void On(unsigned, const Hermes::CommandData&) override {} - - void OnState(unsigned sessionId, Hermes::EState state) override - { - std::cout << "[RPi2] State changed: " << static_cast(state) << "\n"; + void On(unsigned, Hermes::EState, const Hermes::StartTransportData&) override { + std::cout << "[RPi2] === TRANSPORT ENGAGED (Motors ON) ===\n"; } - void OnDisconnected(unsigned sessionId, Hermes::EState state, - const Hermes::Error& error) override - { - std::cout << "[RPi2] RPi1 disconnected"; - if (error) - std::cout << " reason=" << error.m_text; - std::cout << "\n"; - g_activeSession = 0; + void OnDisconnected(unsigned, Hermes::EState, const Hermes::Error& e) override { + g_activeSession = 0; std::cout << "[RPi2] Disconnected: " << e.m_text << "\n"; } - void OnTrace(unsigned, Hermes::ETraceType, Hermes::StringView) override - { - // Uncomment to see full protocol trace: - // std::cout << "[RPi2][TRACE] " << std::string(trace) << "\n"; - } + void On(unsigned, Hermes::EState, const Hermes::MachineReadyData&) override { std::cout << "[RPi2] Upstream Node is READY.\n"; } + void On(unsigned, Hermes::EState, const Hermes::StopTransportData&) override { std::cout << "[RPi2] StopTransport Received. Hardware Reset Verified.\n"; } + void On(unsigned, const Hermes::NotificationData&) override {} + void On(unsigned, const Hermes::CheckAliveData&) override {} + void On(unsigned, const Hermes::CommandData&) override {} + void OnTrace(unsigned, Hermes::ETraceType, Hermes::StringView) override {} + void On(unsigned, Hermes::EState, const Hermes::RevokeMachineReadyData&) override {} }; -int main() -{ - std::signal(SIGINT, signalHandler); - std::signal(SIGTERM, signalHandler); - - std::cout << "[RPi2] Starting Hermes Downstream\n"; - std::cout << "[RPi2] Listening on port " << HERMES_PORT - << " for RPi1...\n"; - +int main() { + std::signal(SIGINT, [](int) { g_running = false; }); + set_raw_mode(true); + DownstreamCallback callback; - Hermes::Downstream downstream(1, callback); // lane 1 - g_downstream = &downstream; - - // Settings: who we are, and which port to listen on - // m_optionalClientAddress is left empty = accept from any IP + Hermes::Downstream downstream(1, callback); + callback.m_pDownstream = &downstream; + Hermes::DownstreamSettings settings; - settings.m_machineId = THIS_MACHINE_ID; - settings.m_port = HERMES_PORT; - settings.m_checkAlivePeriodInSeconds = 60.0; - settings.m_reconnectWaitTimeInSeconds = 5.0; - + settings.m_machineId = "RPi2_Downstream"; + settings.m_port = 50100; + downstream.Enable(settings); - - // Run Hermes event loop in a background thread - std::thread networkThread([&downstream]() { - downstream.Run(); - }); - - std::cout << "[RPi2] Running. Press Ctrl+C to stop.\n"; - - while (g_running) - std::this_thread::sleep_for(std::chrono::milliseconds(200)); - - std::cout << "[RPi2] Shutting down...\n"; + std::thread netThread([&]() { downstream.Run(); }); + + std::cout << "Downstream Node Active. Tap 'b' (BoardAvailable) or 't' (TransportFinished).\n"; + + while (g_running) { + if (kbhit()) { + char c = getchar(); + if (g_activeSession != 0) { + if (c == 'b' || c == 'B') { + g_currentBoardId = "BOARD_" + std::to_string(++g_counter); + downstream.Post([&downstream]() { + Hermes::BoardAvailableData ba; + ba.m_boardId = g_currentBoardId; + downstream.Signal(g_activeSession, ba); + std::cout << "[RPi2] Sent BoardAvailable: " << g_currentBoardId << "\n"; + }); + } + else if (c == 't' || c == 'T') { + downstream.Post([&downstream]() { + Hermes::TransportFinishedData tf; + tf.m_boardId = g_currentBoardId; + tf.m_transferState = Hermes::ETransferState::eCOMPLETE; + downstream.Signal(g_activeSession, tf); + std::cout << "[RPi2] Sent TransportFinished for " << g_currentBoardId << "\n"; + }); + } + else if (c == 'q' || c == 'Q') g_running = false; + } + } + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + } + + set_raw_mode(false); downstream.Stop(); - if (networkThread.joinable()) - networkThread.join(); - - g_downstream = nullptr; - std::cout << "[RPi2] Done.\n"; + if (netThread.joinable()) netThread.join(); return 0; } From ed0ce6987ada02fcefa785b2bddaf07cec1e9ef1 Mon Sep 17 00:00:00 2001 From: sahil agarwal Date: Mon, 30 Mar 2026 02:25:35 +0530 Subject: [PATCH 14/14] fixed documentation and build methods for windows and linux --- CMakeLists.txt | 8 +- README.md | 120 ++- docs/01_Architecture_Overview.md | 45 - docs/02_Building_and_Linking.md | 52 -- docs/03_Modern_API_Basics.md | 55 -- docs/04_Downstream_Receiver.md | 0 docs/05_Upstream_Sender.md | 72 -- docs/06_Core_Message_Types.md | 72 -- docs/07_Network_and_Topology.md | 67 -- docs/08_Legacy_Interface_API.md | 93 -- docs/09_Vertical_MES_Integration.md | 86 -- docs/BUILDING.md | 100 --- src/include/HermesModern.hpp | 1218 ++++++++++++++++++--------- 13 files changed, 941 insertions(+), 1047 deletions(-) delete mode 100644 docs/01_Architecture_Overview.md delete mode 100644 docs/02_Building_and_Linking.md delete mode 100644 docs/03_Modern_API_Basics.md delete mode 100644 docs/04_Downstream_Receiver.md delete mode 100644 docs/05_Upstream_Sender.md delete mode 100644 docs/06_Core_Message_Types.md delete mode 100644 docs/07_Network_and_Topology.md delete mode 100644 docs/08_Legacy_Interface_API.md delete mode 100644 docs/09_Vertical_MES_Integration.md delete mode 100644 docs/BUILDING.md diff --git a/CMakeLists.txt b/CMakeLists.txt index d35d3cb..69607d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,9 +12,11 @@ set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) # 2. Find Boost HEADERS ONLY (No 'COMPONENTS system thread' required!) +if(WIN32) + # Adjust the folder name if your boost version is different (e.g., boost_1_78_0) + set(BOOST_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/References/boost_1_78_0" CACHE PATH "Path to local Boost") +endif() + find_package(Boost 1.66 REQUIRED) add_subdirectory(src/Hermes) - -# (Optional: Add your tests subdirectory here if you want them to build) -# add_subdirectory(test/BoostTestHermes) \ No newline at end of file diff --git a/README.md b/README.md index 26ea7ea..6a1e653 100644 --- a/README.md +++ b/README.md @@ -84,47 +84,123 @@ Downstream machine Upstream machine | `Modern::Upstream` | `ServiceDescription`, `BoardAvailable`, `RevokeBoardAvailable`, `TransportFinished`, `BoardForecast`, `SendBoardInfo` | `ServiceDescription`, `MachineReady`, `RevokeMachineReady`, `StartTransport`, `StopTransport`, `QueryBoardInfo` | --- +# Building the Hermes C++ Library -## 3. Building the library +The Hermes library uses **CMake** as its universal build system. The library has been modernized to use **Header-Only Boost** for its core networking, making it incredibly lightweight and easy to compile natively on Windows, Linux, and macOS without complex binary linking. -### Prerequisites +--- + +## 1. Prerequisites + +Before building, ensure your environment has the following: + +* **C++17 Compiler**: GCC 7+ / Clang 5+ (Linux) or MSVC 2017+ / MinGW-w64 (Windows). +* **CMake**: Version 3.15 or higher. +* **Boost Libraries**: Version 1.66 to 1.78. +* **pugixml**: XML parsing library. + +--- + +### 2. Environment Setup + +### Linux (Ubuntu / Debian) +On Linux, dependencies are easily managed via the system package manager. Install the standard build essentials, Boost, and pugixml directly: ```bash -# Debian / Ubuntu / Raspberry Pi OS -sudo apt install -y g++ cmake libboost-all-dev +sudo apt update && sudo apt upgrade -y +sudo apt install -y g++ cmake libboost-all-dev libpugixml-dev git +``` +### Windows +On Windows, dependencies are managed manually. -# macOS -brew install cmake boost +- Boost (1.66 - 1.78): Download the Boost source or precompiled headers. Extract the contents into a local References/ directory within your project root (e.g., References/boost). -# Windows -# Install Boost via vcpkg: vcpkg install boost +- pugixml: Download the pugixml source and place it in the References/pugixml/ directory. + +(CMake will be configured to point to these local reference folders in the next steps). + +### 3. Standard Build Process +The CMake workflow is generally identical regardless of your operating system. Open a terminal in the root of the cloned repository. + +#### Step 1: Create a Build Directory +Always perform an "out-of-source" build to keep your source tree clean. + +```Bash +mkdir build +cd build ``` -### Build +#### Step 2: Configure the Project -```bash -git clone https://github.com/hermes-org/lib_cpp.git -cd lib_cpp -mkdir build && cd build +For windows run this command to set MSYS2 binaries into the path for this session +```Bash +set PATH=C:\msys64\ucrt64\bin;%PATH% +``` + +Now, Run CMake to generate the build files. + +```Bash cmake .. -make -j4 +cmake --build . --config Release --parallel 4 ``` +(You can remove parallel flag or adjust the number based on your number of cores. I was working on RPi so i used all 4 cores) + +#### Build Outputs +Once the build completes successfully, the compiled binaries will be located in the build/src/Hermes/ directory: -This produces `build/src/Hermes/libhermes.so` (Linux/macOS) or `hermes.dll` (Windows). +- Linux: libhermes.so -### Compile your application +- Windows: hermes.dll / hermes.lib +- macOS: libhermes.dylib + +### Step 3: Building and Running the Tests +The repository includes a test suite (BoostTestHermes) to verify protocol compliance. + +Note: While the core library is header-only, building the tests requires the compiled boost_unit_test_framework binary. + +To run the tests from inside your build directory: + +```Bash +ctest --output-on-failure -C Release +``` +Alternatively, you can run the executable directly: + +Windows: `.\test\BoostTestHermes\Release\BoostTestHermes.exe` + +Linux: `./test/BoostTestHermes/BoostTestHermes` + +## Step 4: Compiling Your Application + +To compile a standalone C++ application that links against the Hermes library, you must point your compiler to the Hermes include directory and the folder containing your newly built binaries. + +Replace `` with the actual path to your cloned repository. + +**For Linux:** ```bash -g++ -std=c++17 -o myapp myapp.cpp \ - -I./src/include \ - -L./build/src/Hermes -lhermes \ +g++ -std=c++17 -o my_app my_app.cpp \ + -I//src/include \ + -L//build/src/Hermes -lhermes \ -lboost_system -lpthread -#change the commands according to your OS +``` +**For Windows:** +```bash +g++ -std=c++17 -o my_app.exe my_app.cpp ^ + -IC:\\src\include ^ + -LC:\\build\src\Hermes -lhermes ^ + -lws2_32 -lmswsock -liphlpapi +``` + +#### Running the compiled application: -# Run (Linux — tell the linker where to find the .so) -LD_LIBRARY_PATH=./build/src/Hermes ./myapp +- Linux: If you receive a "shared object file not found" error, ensure the linker knows where libhermes.so is located: + +```Bash +LD_LIBRARY_PATH=//build/src/Hermes ./my_app ``` +- Windows: Ensure that hermes.dll is either in the same directory as my_app.exe, or that its folder is added to your system's PATH. + --- ## 4. Header map diff --git a/docs/01_Architecture_Overview.md b/docs/01_Architecture_Overview.md deleted file mode 100644 index a0533f5..0000000 --- a/docs/01_Architecture_Overview.md +++ /dev/null @@ -1,45 +0,0 @@ -# 01. Architecture Overview - -The Hermes Standard (IPC-HERMES-9852) is a protocol based on TCP/IP and XML designed to replace the legacy SMEMA wiring standard. This C++ library serves as a robust wrapper around the protocol, handling the low-level socket connections, XML serialization, and the strict state machines required for compliance. - -To effectively use this library, you must understand three core architectural concepts: **Machine Roles**, the **Event-Driven Model**, and **Thread Management**. - ---- - -## 1. Machine Roles (Network Topology) - -In a Hermes line, machines communicate horizontally (machine-to-machine) and vertically (machine-to-factory). The library provides dedicated classes for each role: - -### Horizontal Integration (The SMT Line) -* **`Downstream` (The Receiver):** Acts as a **TCP Server**. A downstream machine (e.g., a Pick & Place) opens a specific port and listens. It waits for the previous machine in the line to connect and hand over a board. -* **`Upstream` (The Sender):** Acts as a **TCP Client**. An upstream machine (e.g., a Printer) actively connects to the IP address and port of the next machine in the line to send a board. - -*Note: Most machines in the middle of a line will instantiate **both** an `Upstream` object (to send to the right) and a `Downstream` object (to receive from the left).* - -### Vertical Integration (The Factory) -* **`ConfigurationService`:** Used to exchange machine capabilities and supervisory line control data. -* **`VerticalService`:** Acts as a TCP Server to provide real-time board tracking data to higher-level MES/ERP systems (often working alongside IPC-CFX). - ---- - -## 2. The Event-Driven Model - -The library is entirely asynchronous and event-driven. You do not write code that says `WaitForBoard()`. Instead, you register **Callbacks** that the library triggers when network events occur. - -If you are using the modern wrapper (`HermesModern.hpp`), these events are handled via standard C++ lambdas: - -```cpp -// Example: The library triggers this lambda when an XML message arrives -downstream.RegisterBoardAvailableCallback([](const Hermes::BoardAvailableData& board) { - std::cout << "Board Arrived! Barcode: " << board._topBarcode << std::endl; -}); -3. Thread Management (Critical) -Because the library utilizes Boost.ASIO for high-performance networking, it relies on a continuous event loop to process incoming socket data. - -The Run() Loop: All network processing happens inside the Run() method of the Upstream or Downstream objects. - -Blocking Behavior: The Run() method is blocking. If you call it on your main application thread, your application's UI or main control loop will freeze. - -The Solution: You must always execute the core Hermes loop in a dedicated background std::thread. - -(Note: If you use the HermesModern.hpp wrapper, this background thread is automatically managed for you when you call Enable()). \ No newline at end of file diff --git a/docs/02_Building_and_Linking.md b/docs/02_Building_and_Linking.md deleted file mode 100644 index d55b451..0000000 --- a/docs/02_Building_and_Linking.md +++ /dev/null @@ -1,52 +0,0 @@ -# 02. Building and Linking - -The Hermes library uses **CMake (3.15+)** as its universal build system, ensuring seamless, cross-platform compilation on Windows, Linux, and macOS. - ---- - -## 1. System Requirements - -Before you begin, verify your environment has the following: -* **C++17 Compiler:** MSVC 2017+, GCC 7+, or Clang 5+. -* **Boost Libraries:** Version 1.66 through 1.78 is recommended. The library specifically requires `boost_system` and `boost_thread`. - * *Note on Boost 1.87+: If using a newer version of Boost where `io_service` is deprecated, our CMake setup automatically applies the `-DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT` flag to ensure it still compiles.* -* **pugixml:** No installation required. The required files are bundled locally in the `References/` folder. - ---- - -## 2. Compiling the Library - -Open a terminal in the root of the cloned repository and execute a standard out-of-source build: - -```bash -mkdir build && cd build - -# 1. Configure the project (CMake locates Boost and your compiler) -cmake .. - -# 2. Compile the core library -cmake --build . --config Release -Build Outputs: - -Windows: build/Release/hermes.dll and hermes.lib - -Linux: build/libhermes.so - -3. Linking Hermes to Your Project -The cleanest way to integrate Hermes into your own C++ application is by using CMake's add_subdirectory command. - -Assuming you cloned this repository into a folder named third_party/lib_cpp inside your project, your application's CMakeLists.txt would look like this: - -CMake -cmake_minimum_required(VERSION 3.15) -project(MySmtMachine) - -# 1. Include the Hermes library directory -add_subdirectory(third_party/lib_cpp) - -# 2. Define your application executable -add_executable(MyApp main.cpp) - -# 3. Link Hermes to your application -# This automatically configures the include paths for Hermes.hpp -target_link_libraries(MyApp PRIVATE hermes) \ No newline at end of file diff --git a/docs/03_Modern_API_Basics.md b/docs/03_Modern_API_Basics.md deleted file mode 100644 index 34b5836..0000000 --- a/docs/03_Modern_API_Basics.md +++ /dev/null @@ -1,55 +0,0 @@ -# 03. Modern API Basics - -The legacy Hermes C++ library relies on strict class inheritance (`IDownstreamCallback` and `IUpstreamCallback`) and manual thread management. To simplify development, we provide `HermesModern.hpp`, a wrapper that allows you to use modern C++17 lambdas and automatically manages the background networking threads. - ---- - -## 1. Instantiating the Node - -Every Hermes connection requires a `laneId` (typically `1` for a standard single-lane machine). You instantiate either a `Downstream` (Receiver) or `Upstream` (Sender) object from the `Hermes::Modern` namespace. - -```cpp -#include "HermesModern.hpp" - -// Create a receiver (listens for the previous machine) on Lane 1 -Hermes::Modern::Downstream receiver(1); - -// Create a sender (connects to the next machine) on Lane 1 -Hermes::Modern::Upstream sender(1); -2. Registering Callbacks -Instead of overriding pure virtual functions in a separate class, you register lambda functions directly to the object. You only need to register the callbacks your machine actually cares about. - -C++ -// Handle successful TCP connections -receiver.RegisterConnectedCallback([](const Hermes::ConnectionInfo& info) { - std::cout << "Connected to: " << info.m_hostAddress << "\n"; -}); - -// Handle incoming board data -receiver.RegisterBoardAvailableCallback([](const Hermes::BoardAvailableData& board) { - std::cout << "Incoming Board ID: " << board.m_boardId << "\n"; -}); - -// Handle disconnection or errors -receiver.RegisterDisconnectedCallback([](const Hermes::Error& err) { - std::cout << "Disconnected. Reason: " << err.m_text << "\n"; -}); -3. Configuration and Execution (Enable) -To start listening (Downstream) or actively connecting (Upstream), you must populate a settings object and pass it to the Enable() method. - -Crucially, calling Enable() in the modern wrapper automatically spawns the required background std::thread to process network events. You do not need to manage the blocking Run() loop yourself. - -C++ -Hermes::DownstreamSettings settings; -settings.m_machineId = "My_Pick_And_Place"; -settings.m_clientAddress = "192.168.1.100"; // Accept connections from this IP -settings.m_port = 50101; // Standard Hermes Default Port - -// Start the network event loop in the background -receiver.Enable(settings); -4. Shutting Down (Stop) -When your application is closing, or if you need to sever the connection to change line configurations, call Stop(). This will cleanly disconnect the socket, send the appropriate XML termination messages, and safely join the background thread. - -C++ -// Safely shut down the connection and stop the background thread -receiver.Stop(); \ No newline at end of file diff --git a/docs/04_Downstream_Receiver.md b/docs/04_Downstream_Receiver.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/05_Upstream_Sender.md b/docs/05_Upstream_Sender.md deleted file mode 100644 index fda4ee4..0000000 --- a/docs/05_Upstream_Sender.md +++ /dev/null @@ -1,72 +0,0 @@ -# 05. Upstream Sender (The Handshake) - -An **Upstream** node acts as the sending machine (e.g., a printer sending a board to a pick-and-place). It operates as a TCP Client, actively attempting to connect to the IP address and port of the next machine in the line. - -To successfully hand over a board, you must follow the exact reverse of the Downstream handshake. - ---- - -## 1. The Standard Handshake Sequence - -If you send messages out of sequence, the receiving machine is required by the Hermes Standard to drop the connection. - -| Upstream (Sender - YOU) | Direction | Downstream (Receiver) | -| :--- | :---: | :--- | -| `ServiceDescription` | ➔ | *(Establish protocol version)* | -| | ⬅ | `MachineReady` *(I have space for a board)* | -| `BoardAvailable` | ➔ | *(Contains Barcode/Dimensions)* | -| | ⬅ | `StartTransport` *(Conveyors turning, send it!)* | -| `TransportFinished`| ➔ | *(Board has left my machine)* | -| | ⬅ | `StopTransport` *(Board is fully inside me)* | - ---- - -## 2. Implementing the Sender Logic - -Using the `HermesModern.hpp` wrapper, here is how you implement a compliant sender. - -*(Note: In a real machine application, you will also integrate your physical sensors—like board edge detectors—into this logic flow).* - -```cpp -#include "HermesModern.hpp" -#include - -Hermes::Modern::Upstream sender(1); -const unsigned CURRENT_SESSION = 1; - -// 1. Wait for the Downstream machine to say it is empty -sender.RegisterMachineReadyCallback([&](const Hermes::MachineReadyData& data) { - std::cout << "Downstream is ready to receive a board.\n"; - - // 2. When your machine finishes processing a board, announce it - Hermes::BoardAvailableData boardData; - boardData.m_boardId = "PCB_12345"; - boardData.m_topBarcode = "SN-998877"; - boardData.m_lengthInMM = 150.0f; - boardData.m_widthInMM = 100.0f; - sender.Signal(CURRENT_SESSION, boardData); -}); - -// 3. Downstream has started its conveyors and says "Send it" -sender.RegisterStartTransportCallback([&](const Hermes::StartTransportData& data) { - std::cout << "Downstream conveyors running at " << data.m_conveyorSpeed << " mm/s\n"; - - // 4. Start YOUR conveyors to physically move the board out. - // ... (Wait for hardware sensor to confirm board left the machine) ... - - // 5. Tell the Downstream machine the board is fully on their side - Hermes::TransportFinishedData finishedData; - finishedData.m_transferState = Hermes::ETransferState::eCOMPLETE; - sender.Signal(CURRENT_SESSION, finishedData); -}); - -// 6. Downstream confirms the board has safely arrived inside -sender.RegisterStopTransportCallback([&](const Hermes::StopTransportData& data) { - std::cout << "Handover Complete! Board safely delivered.\n"; - - // You are now free to process the next board. -}); -3. Handling Exceptions -RevokeMachineReady: If you receive this before sending BoardAvailable, it means the downstream machine suddenly became unavailable (e.g., an operator hit the Emergency Stop). You must wait until you receive a new MachineReady message before trying to send your board. - -Connection Loss: If the connection drops during transport, halt your conveyors immediately. \ No newline at end of file diff --git a/docs/06_Core_Message_Types.md b/docs/06_Core_Message_Types.md deleted file mode 100644 index 376b066..0000000 --- a/docs/06_Core_Message_Types.md +++ /dev/null @@ -1,72 +0,0 @@ -# 06. Core Message Types & Data Dictionary - -The Hermes Standard defines specific payloads for every stage of the handshake. This document provides a quick reference for the exact C++ structs and fields you will interact with in `HermesData.hpp`. - -## ⚠️ Important Note on `Hermes::Optional` -To maintain compatibility with older C++ compilers, this library does not use `std::optional`. It uses a custom `Hermes::Optional`. To safely read optional fields like barcodes or dimensions, check them like a boolean and dereference them with `*`: - -```cpp -if (boardData.m_optionalTopBarcode) { - std::string barcode = *boardData.m_optionalTopBarcode; -} -1. Initialization Messages -ServiceDescriptionData -Exchanged immediately upon TCP connection to verify compatibility. - -std::string m_machineId: The name/ID of the machine. - -unsigned m_laneId: The specific lane (usually 0 or 1). - -std::string m_version: Protocol version (Defaults to "1.5"). - -SupportedFeatures m_supportedFeatures: Flags for advanced features. - -2. Board Handover Messages -BoardAvailableData (Sent by Upstream) -Announces a board is waiting to be sent. Contains physical properties. -Required Fields: - -std::string m_boardId: Unique UUID for the board. - -std::string m_boardIdCreatedBy: Machine ID that generated the UUID. - -EBoardQuality m_failedBoard: eANY, eGOOD, or eBAD. - -EFlippedBoard m_flippedBoard: eSIDE_UP_IS_UNKNOWN, eTOP_SIDE_IS_UP, eBOTTOM_SIDE_IS_UP. - -Common Optional Fields: - -Optional m_optionalTopBarcode / m_optionalBottomBarcode - -Optional m_optionalLengthInMM / m_optionalWidthInMM / m_optionalThicknessInMM - -Optional m_optionalWorkOrderId / m_optionalBatchId - -MachineReadyData (Sent by Downstream) -Announces the receiver is empty and ready. - -EBoardQuality m_failedBoard: Specifies what quality the machine accepts (e.g., eGOOD). - -Note: This struct also contains the same optional physical dimensions as BoardAvailableData. Downstream machines can populate these to demand specific board sizes. - -3. Transport Control Messages -StartTransportData (Sent by Downstream) -Commands the Upstream machine to begin pushing the board. - -std::string m_boardId: The UUID of the board being requested. - -Optional m_optionalConveyorSpeedInMMPerSecs: The speed the sender should match. - -TransportFinishedData (Sent by Upstream) -Confirms the board has entirely left the sender's conveyor. - -ETransferState m_transferState: Usually ETransferState::eCOMPLETE. - -std::string m_boardId: The UUID of the transferred board. - -StopTransportData (Sent by Downstream) -Confirms the board has securely arrived inside the receiver. - -ETransferState m_transferState: Usually ETransferState::eCOMPLETE. - -std::string m_boardId: The UUID of the received board. \ No newline at end of file diff --git a/docs/07_Network_and_Topology.md b/docs/07_Network_and_Topology.md deleted file mode 100644 index dcc16e5..0000000 --- a/docs/07_Network_and_Topology.md +++ /dev/null @@ -1,67 +0,0 @@ -# 07. Network Configuration & Topology - -Hermes operates over standard TCP/IP Ethernet. Because it uses standard networking rather than proprietary cables, machines can be wired together in different ways depending on the factory's infrastructure. - -This document explains the physical setups and how to configure your `UpstreamSettings` and `DownstreamSettings` structs to match them. - ---- - -## 1. Physical Topologies - -### Point-to-Point (Direct Connection) -This is the simplest setup and the direct equivalent of legacy SMEMA. Two adjacent machines are connected directly to each other using a standard Ethernet cable. -* **Pros:** Extremely secure; no interference from other factory traffic. -* **Cons:** Requires the machine to have multiple network interface cards (NICs) if it also needs to talk to the factory network (MES). - -### Switched Fabric (Smart Factory) -All machines in the line plug into a central factory Ethernet switch. -* **Pros:** A single physical cable handles both horizontal (machine-to-machine) and vertical (machine-to-MES) data. -* **Cons:** Requires strict IP filtering to ensure Machine A doesn't accidentally send a board to Machine C. - ---- - -## 2. The Hermes Port Standard - -By definition (IPC-HERMES-9852), the TCP port used for a connection is derived from the Lane ID. -* The Base Port is always **50100**. -* Port = Base Port + Lane ID. -* **Therefore, a standard single-lane machine ALWAYS communicates on Port 50101.** - ---- - -## 3. Configuring the Receiver (`DownstreamSettings`) - -The Receiver acts as a TCP Server. It opens a port and waits. - -```cpp -Hermes::DownstreamSettings settings; -settings.m_machineId = "Oven_01"; -settings.m_port = 50101; - -// OPTIONAL BUT RECOMMENDED IN SWITCHED NETWORKS: -// If you leave this blank, ANY machine on the factory floor can connect to your receiver. -// By setting this, you force the socket to ONLY accept connections from the specific -// IP address of the machine physically positioned before you in the line. -settings.m_optionalClientAddress = "192.168.1.50"; - -// Advanced keep-alive timings (defaults are usually fine) -settings.m_checkAlivePeriodInSeconds = 60.0; -settings.m_reconnectWaitTimeInSeconds = 10.0; -4. Configuring the Sender (UpstreamSettings) -The Sender acts as a TCP Client. It actively reaches out across the network to find the next machine. - -C++ -Hermes::UpstreamSettings settings; -settings.m_machineId = "Printer_01"; -settings.m_port = 50101; - -// REQUIRED: -// You must explicitly tell the sender the IP address of the Downstream -// receiver it is supposed to push boards into. -settings.m_hostAddress = "192.168.1.51"; - -// Advanced keep-alive timings -settings.m_checkAlivePeriodInSeconds = 60.0; -settings.m_reconnectWaitTimeInSeconds = 10.0; -5. Network Resilience -The library handles network drops automatically. If a cable is unplugged, the OnDisconnected callback will fire. The Upstream node will then automatically attempt to reconnect to the m_hostAddress every m_reconnectWaitTimeInSeconds (default: 10 seconds) until the connection is restored. \ No newline at end of file diff --git a/docs/08_Legacy_Interface_API.md b/docs/08_Legacy_Interface_API.md deleted file mode 100644 index 00306d7..0000000 --- a/docs/08_Legacy_Interface_API.md +++ /dev/null @@ -1,93 +0,0 @@ -# 08. Legacy Interface API (Advanced) - -While the `HermesModern.hpp` wrapper simplifies development using lambdas, advanced users may prefer to interact directly with the raw C++ library to minimize overhead or to integrate Hermes deeply into an existing class hierarchy. - -To use the raw API, you must inherit from the core interfaces and manage the blocking event loops manually. - ---- - -## 1. The Core Interfaces - -The library provides two primary abstract classes: -* `Hermes::IDownstreamCallback` -* `Hermes::IUpstreamCallback` - -If you inherit directly from these, your compiler will force you to implement every pure virtual method (e.g., `OnConnected`, `OnState`, and the various overloaded `On` methods for data). - ---- - -## 2. The Naming Collision Problem - -In a real factory, most machines sit in the middle of the line. This means your application class will likely need to act as both a Sender and a Receiver. - -If you try to inherit from both base interfaces simultaneously, you will encounter **ambiguous function names**. For example, both interfaces define this exact method: -`virtual void On(unsigned sessionId, EState state, const ServiceDescriptionData& data) = 0;` - -The compiler won't know if you are receiving a service description from the previous machine or the next machine. - ---- - -## 3. The "Helper" Solution - -To solve the naming collision, the library provides two specialized helper structs in `Hermes.hpp`: -* `Hermes::UpstreamCallbackHelper` -* `Hermes::DownstreamCallbackHelper` - -These helpers internally catch the ambiguous `On()` methods and reroute them to uniquely named functions like `OnUpstream(...)` and `OnDownstream(...)`. - ---- - -## 4. Legacy Implementation Example - -Here is how you use the helpers to build a machine that sends and receives simultaneously using the raw API: - -```cpp -#include "Hermes.hpp" -#include -#include - -// 1. Inherit from both Helpers, NOT the base interfaces -class MySmtMachine : public Hermes::UpstreamCallbackHelper, - public Hermes::DownstreamCallbackHelper -{ -public: - // 2. Instantiate the core objects, passing `*this` as the callback reference - MySmtMachine() : m_upstream(1, *this), m_downstream(1, *this) {} - - void Start() { - // 3. YOU must manage the blocking threads manually - m_upThread = std::thread([this]() { m_upstream.Run(); }); - m_downThread = std::thread([this]() { m_downstream.Run(); }); - - Hermes::UpstreamSettings upSettings("MyMachine", "192.168.1.51", 50101); - Hermes::DownstreamSettings downSettings("MyMachine", 50101); - - m_upstream.Enable(upSettings); - m_downstream.Enable(downSettings); - } - - void Stop() { - m_upstream.Stop(); - m_downstream.Stop(); - if (m_upThread.joinable()) m_upThread.join(); - if (m_downThread.joinable()) m_downThread.join(); - } - -protected: - // 4. Implement the uniquely named virtual methods - void OnUpstreamConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { - std::cout << "Sender Connected to next machine!\n"; - } - - void OnDownstreamConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { - std::cout << "Receiver Accepted connection from previous machine!\n"; - } - - // ... You must implement all remaining OnUpstream and OnDownstream virtuals ... - -private: - Hermes::Upstream m_upstream; - Hermes::Downstream m_downstream; - std::thread m_upThread; - std::thread m_downThread; -}; \ No newline at end of file diff --git a/docs/09_Vertical_MES_Integration.md b/docs/09_Vertical_MES_Integration.md deleted file mode 100644 index 4788afb..0000000 --- a/docs/09_Vertical_MES_Integration.md +++ /dev/null @@ -1,86 +0,0 @@ -# 09. Vertical Integration (MES/ERP) - -While `Upstream` and `Downstream` handle **Horizontal** communication (machine passing boards to machine), the Hermes Standard also defines a **Vertical** channel. This allows higher-level factory software (like an MES, ERP, or line controller) to monitor and control the machines in real-time. - -In the ASM `lib_cpp` library, this is handled by the `VerticalService` and `VerticalClient` classes. - ---- - -## 1. The Architecture - -In a standard smart factory setup: -* **The Machine (You):** Acts as the **`VerticalService`** (TCP Server). You open a port (often `1248` or `50100`) and wait. -* **The MES/Factory Cloud:** Acts as the **`VerticalClient`** (TCP Client). It connects to every machine on the line to gather data. - ---- - -## 2. The Vertical Handshake - -When an MES connects to your machine, you must exchange supervisory descriptions. This is entirely separate from the machine-to-machine horizontal handshake. - -1. **MES Connects:** Your `OnConnected` callback fires. -2. **Exchange Descriptions:** The MES sends its `SupervisoryServiceDescriptionData`. Your machine responds with its own, detailing what features it supports (e.g., `FeatureBoardTracking`, `FeatureQueryWorkOrderInfo`). - ---- - -## 3. Real-Time Board Tracking - -The most common use case for Vertical Integration is letting the factory know exactly where every PCB is located. If the MES enables `FeatureBoardTracking`, your machine is responsible for firing off two specific messages during its operation: - -### A. `BoardArrivedData` -You must signal this to the `VerticalService` the moment a board successfully enters your machine. -**Key Fields:** -* `m_machineId`: Your machine's ID. -* `m_boardId`: The UUID of the board that just entered. -* `m_boardTransfer`: How it got there (e.g., `EBoardArrivedTransfer::eTRANSFERRED` from an upstream machine, or `eLOADED` if an operator manually placed it). - -### B. `BoardDepartedData` -You must signal this the moment a board completely leaves your machine. -**Key Fields:** -* `m_machineId`: Your machine's ID. -* `m_boardId`: The UUID of the board. -* `m_boardTransfer`: How it left (e.g., `EBoardDepartedTransfer::eTRANSFERRED` to a downstream machine, or `eREMOVED` if an operator took it out). - ---- - -## 4. Advanced: Work Orders and Routing - -If your machine supports dynamic recipes or routing, the Vertical channel allows you to ask the MES what to do with a board. - -1. **Query:** When a board arrives, your machine sends a `QueryWorkOrderInfoData` containing the board's barcode. -2. **Reply:** The MES responds with `ReplyWorkOrderInfoData`, telling your machine which recipe/program to load for that specific barcode. -3. **Route:** The MES can also inject `m_route` attributes, telling a sorting conveyor whether to pass the board through, send it to an inspection queue, or reject it to a scrap bin. - ---- - -## 5. Raw Implementation Example - -If you are using the raw C++ interface (bypassing lambdas), your vertical implementation will look like this: - -```cpp -#include "Hermes.hpp" - -class MyLineMonitor : public Hermes::IVerticalServiceCallback { -public: - MyLineMonitor() : m_verticalService(*this) {} - - void Start() { - Hermes::VerticalServiceSettings settings("My_Machine", 1248); - m_verticalService.Enable(settings); - // Ensure you run m_verticalService.Run() in a background thread! - } - - // --- Override the pure virtuals --- - void OnConnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::ConnectionInfo& info) override { - // MES has connected to us - } - - void On(unsigned sessionId, Hermes::EVerticalState state, const Hermes::SupervisoryServiceDescriptionData& data) override { - // MES told us who it is. We can now reply with our capabilities. - } - - // ... Implement remaining virtuals ... - -private: - Hermes::VerticalService m_verticalService; -}; \ No newline at end of file diff --git a/docs/BUILDING.md b/docs/BUILDING.md deleted file mode 100644 index 36d59b1..0000000 --- a/docs/BUILDING.md +++ /dev/null @@ -1,100 +0,0 @@ -# Building the Hermes C++ Library - -The Hermes library uses **CMake** as its universal build system. The library has been modernized to use **Header-Only Boost** for its core networking, making it incredibly lightweight and easy to compile natively on Windows, Linux, and macOS without complex binary linking. - ---- - -## 1. Prerequisites - -Before building, ensure your environment has the following installed: - -* **C++17 Compiler**: - * **Linux**: GCC 7+ or Clang 5+ - * **Windows**: MSYS2/MinGW-w64 (GCC) or MSVC (Visual Studio 2017+) - * **macOS**: Apple Clang -* **CMake**: Version 3.15 or higher. -* **Boost Libraries**: Version 1.66 or newer. *(Note: The core library only requires Boost headers. No compiled `.lib` or `.so` Boost binaries are required unless you are building the Test Suite).* -* **pugixml**: Included locally in the `References/` directory, so no external installation is required. - ---- - -## 2. Environment Setup (From Scratch) - -Depending on your operating system, follow the steps below to ensure you have all required build tools present before running CMake. - -### 🐧 Linux (Ubuntu / Debian) -Linux is the easiest platform to build on. Simply install the standard build essentials and Boost headers from your package manager: -```bash -sudo apt update && sudo apt upgrade -y -sudo apt install -y g++ cmake libboost-all-dev git -🪟 Windows (via MSYS2 / MinGW - Recommended) -To build a lightweight .dll using GCC on Windows, use MSYS2 (UCRT64 environment). Open your MSYS2 terminal and install the toolchain: - -Bash -pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-make mingw-w64-ucrt-x86_64-boost -Crucial: If you build from a standard Windows Command Prompt (cmd) instead of the MSYS2 terminal, you must temporarily add the MSYS2 binaries to your path before running CMake: - -DOS -set PATH=C:\msys64\ucrt64\bin;%PATH% -🪟 Windows (via Visual Studio / MSVC) -If you prefer Microsoft Visual Studio, ensure the "Desktop development with C++" workload is installed. You will also need to download Boost headers (e.g., from boost.org) and extract them to a local directory (e.g., C:\local\boost_1_84_0). - -3. Standard Build Process -The CMake workflow is generally identical regardless of your operating system. Open a terminal in the root of the cloned repository. - -Step 1: Create a Build Directory -Always perform an "out-of-source" build to keep your source tree clean. - -Bash -mkdir build -cd build -Step 2: Configure the Project -Run CMake to generate the build files. - -For Linux / macOS: - -Bash -cmake .. -make -j4 -For Windows (MSYS2 / MinGW): - -DOS -cmake .. -G "MinGW Makefiles" -For Windows (Visual Studio): - -DOS -cmake .. -DBOOST_ROOT="C:/local/boost_1_84_0" -Step 3: Compile the Library -Execute the build command to compile the library. - -Bash -cmake --build . --config Release -Build Outputs -Once the build completes successfully, the compiled binaries will be located in the build/src/Hermes/ directory: - -Windows: hermes.dll - -Linux: libhermes.so - -macOS: libhermes.dylib - -4. Building and Running the Tests -The repository includes a test suite (BoostTestHermes) to verify protocol compliance. - -Note: While the core library is header-only, building the tests requires the compiled boost_unit_test_framework binary. - -To run the tests from inside your build directory: - -Bash -ctest --output-on-failure -C Release -Alternatively, you can run the executable directly: - -Windows: .\test\BoostTestHermes\Release\BoostTestHermes.exe - -Linux: ./test/BoostTestHermes/BoostTestHermes - -To compile any file run -g++ -std=c++17 -o example.exe example.cpp \ - -I~/lib_cpp/src/include \ - -L~/lib_cpp/build -lhermes \ - -lboost_system -lpthread \ No newline at end of file diff --git a/src/include/HermesModern.hpp b/src/include/HermesModern.hpp index a2e42a9..88ed21e 100644 --- a/src/include/HermesModern.hpp +++ b/src/include/HermesModern.hpp @@ -1,521 +1,979 @@ -// ============================================================================== -// src/include/HermesModern.hpp -// Modern C++ Wrapper for the Hermes Standard Library (Complete Suite) -// ============================================================================== +/*********************************************************************** +Copyright 2018 ASM Assembly Systems GmbH & Co. KG + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +************************************************************************/ + +// Copyright (c) ASM Assembly Systems GmbH & Co. KG +// +// HermesModern.hpp +// ---------------- +// Modern C++17 callback-based wrappers over the Hermes C++ interface (Hermes.hpp). +// +// These wrappers replace the pure-virtual callback interface with std::function +// registrations, removing the need to subclass IDownstreamCallback etc. directly. +// +// Covered classes: +// Hermes::Modern::Downstream -- horizontal lane, downstream side (receives from upstream machine) +// Hermes::Modern::Upstream -- horizontal lane, upstream side (receives from downstream machine) +// Hermes::Modern::VerticalService -- vertical interface, service/server side +// Hermes::Modern::VerticalClient -- vertical interface, client side +// Hermes::Modern::ConfigurationService -- configuration service +// +// Threading model: +// Each class spawns one internal thread that runs the Hermes IO loop. +// Enable() must be called after the IO loop has started; it is posted into +// the loop via Post() to guarantee correct sequencing. +// All registered callbacks are invoked from the IO thread. +// +// Usage example (Downstream): +// +// Hermes::Modern::Downstream ds(1); +// ds.RegisterBoardAvailableCallback([](const Hermes::BoardAvailableData& d) { +// // handle board available +// }); +// Hermes::DownstreamSettings settings("MyMachine", 50101); +// ds.Enable(settings); +// // ... +// ds.Stop(); + #pragma once #include "Hermes.hpp" -#include -#include + #include +#include #include +#include namespace Hermes { namespace Modern { -// ============================================================================== -// Downstream Wrapper (Receiver - Horizontal) -// ============================================================================== -class Downstream { +// ============================================================================= +// Downstream +// +// Wraps Hermes::Downstream (horizontal lane, downstream port). +// The downstream side *receives* messages sent by the upstream machine: +// ServiceDescriptionData, MachineReadyData, RevokeMachineReadyData, +// StartTransportData, StopTransportData, QueryBoardInfoData (optional), +// NotificationData, CheckAliveData (optional), CommandData. +// +// The downstream side *sends*: +// ServiceDescriptionData, BoardAvailableData, RevokeBoardAvailableData, +// TransportFinishedData, BoardForecastData, SendBoardInfoData, +// NotificationData, CheckAliveData, CommandData. +// ============================================================================= +class Downstream +{ public: - using ConnectedCallback = std::function; - using DisconnectedCallback = std::function; - using StateChangeCallback = std::function; - using TraceCallback = std::function; - - using ServiceDescriptionCallback = std::function; - using MachineReadyCallback = std::function; - using RevokeMachineReadyCallback = std::function; - using StartTransportCallback = std::function; - using StopTransportCallback = std::function; - using QueryBoardInfoCallback = std::function; - - using NotificationCallback = std::function; - using CheckAliveCallback = std::function; - using CommandCallback = std::function; - - Downstream(unsigned laneId) : m_laneId(laneId), m_isRunning(false) { - m_callbackWrapper = std::make_unique(this); - m_downstream = std::make_unique(laneId, *m_callbackWrapper); + // ---- Connection lifecycle callbacks ---- + + /// Called when a TCP connection is established with the upstream machine. + using ConnectedCallback = std::function; + /// Called when the TCP connection is dropped. + using DisconnectedCallback = std::function; + /// Called on every protocol state machine transition. + using StateCallback = std::function; + /// Called for every internal trace/log event. Useful for debugging. + using TraceCallback = std::function; + + // ---- Received message callbacks (sent by the upstream machine) ---- + + /// Received: upstream declares its service capabilities. + using ServiceDescriptionCallback = std::function; + /// Received: upstream signals it is ready to accept a board. + using MachineReadyCallback = std::function; + /// Received: upstream revokes a previously sent MachineReady. + using RevokeMachineReadyCallback = std::function; + /// Received: upstream commands the conveyor to start moving the board. + using StartTransportCallback = std::function; + /// Received: upstream commands the conveyor to stop. + using StopTransportCallback = std::function; + /// Received: upstream requests board information (optional message). + using QueryBoardInfoCallback = std::function; + /// Received: upstream sends a notification (e.g. error, shutdown). + using NotificationCallback = std::function; + /// Received: upstream sends a check-alive ping/pong (optional message). + using CheckAliveCallback = std::function; + /// Received: upstream sends a command. + using CommandCallback = std::function; + + /// @param laneId The lane number this downstream port belongs to (1-based). + explicit Downstream(unsigned laneId) + : m_laneId(laneId) + { + m_callbackWrapper = std::make_unique(this); + m_downstream = std::make_unique(laneId, *m_callbackWrapper); } ~Downstream() { Stop(); } - void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } - void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } - void RegisterStateChangeCallback(StateChangeCallback cb) { m_onStateChange = cb; } - void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } - - void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb) { m_onServiceDescription = cb; } - void RegisterMachineReadyCallback(MachineReadyCallback cb) { m_onMachineReady = cb; } - void RegisterRevokeMachineReadyCallback(RevokeMachineReadyCallback cb) { m_onRevokeMachineReady = cb; } - void RegisterStartTransportCallback(StartTransportCallback cb) { m_onStartTransport = cb; } - void RegisterStopTransportCallback(StopTransportCallback cb) { m_onStopTransport = cb; } - void RegisterQueryBoardInfoCallback(QueryBoardInfoCallback cb) { m_onQueryBoardInfo = cb; } - void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } - void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } - void RegisterCommandCallback(CommandCallback cb) { m_onCommand = cb; } - - void Enable(const DownstreamSettings& settings) { - if (m_isRunning) return; - m_downstream->Enable(settings); - m_isRunning = true; - m_networkThread = std::thread([this]() { m_downstream->Run(); }); + Downstream(const Downstream&) = delete; + Downstream& operator=(const Downstream&) = delete; + + // ---- Callback registration ---- + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = std::move(cb); } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = std::move(cb); } + void RegisterStateCallback(StateCallback cb) { m_onState = std::move(cb); } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = std::move(cb); } + void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb) { m_onServiceDescription = std::move(cb); } + void RegisterMachineReadyCallback(MachineReadyCallback cb) { m_onMachineReady = std::move(cb); } + void RegisterRevokeMachineReadyCallback(RevokeMachineReadyCallback cb) { m_onRevokeMachineReady = std::move(cb); } + void RegisterStartTransportCallback(StartTransportCallback cb) { m_onStartTransport = std::move(cb); } + void RegisterStopTransportCallback(StopTransportCallback cb) { m_onStopTransport = std::move(cb); } + void RegisterQueryBoardInfoCallback(QueryBoardInfoCallback cb) { m_onQueryBoardInfo = std::move(cb); } + void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = std::move(cb); } + void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = std::move(cb); } + void RegisterCommandCallback(CommandCallback cb) { m_onCommand = std::move(cb); } + + // ---- Lifecycle ---- + + /// Start the IO loop and enable the downstream port. + /// Enable() is posted into the IO thread to guarantee it executes after Run() starts. + /// Safe to call only once; subsequent calls while running are no-ops. + void Enable(const DownstreamSettings& settings) + { + if (m_isRunning.exchange(true)) + return; + + m_networkThread = std::thread([this, settings]() + { + m_downstream->Post([this, settings]() + { + m_downstream->Enable(settings); + }); + m_downstream->Run(); + }); } - void Stop() { - if (m_isRunning) { - m_downstream->Stop(); - if (m_networkThread.joinable()) m_networkThread.join(); - m_isRunning = false; - } + /// Stop the IO loop and join the thread. Safe to call multiple times. + void Stop() + { + if (!m_isRunning.exchange(false)) + return; + m_downstream->Stop(); + if (m_networkThread.joinable()) + m_networkThread.join(); } + // ---- Sending messages ---- + // These must be called from the IO thread (e.g. from within a callback, + // or posted via Post()). Calling from an external thread is safe only + // if done via Post(). + + /// Send a message to the connected upstream machine. template void Signal(unsigned sessionId, const T& data) { m_downstream->Signal(sessionId, data); } + /// Post a callable into the IO thread. Use this to call Signal() safely + /// from outside the IO thread. + template + void Post(F&& f) { m_downstream->Post(std::forward(f)); } + + /// Reset all sessions with a notification (e.g. on shutdown or config change). + void Reset(const NotificationData& data) { m_downstream->Reset(data); } + + /// Disable the port gracefully with a notification. + void Disable(const NotificationData& data) { m_downstream->Disable(data); } + private: - class InternalCallbackWrapper : public Hermes::IDownstreamCallback { - Downstream* m_parent; + // Bridges Hermes::IDownstreamCallback pure virtuals to our std::function members. + class CallbackWrapper : public Hermes::IDownstreamCallback + { public: - InternalCallbackWrapper(Downstream* parent) : m_parent(parent) {} + explicit CallbackWrapper(Downstream* parent) : m_parent(parent) {} - void OnConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { - if (m_parent->m_onConnected) m_parent->m_onConnected(info); + void OnConnected(unsigned sessionId, EState state, const ConnectionInfo& info) override + { + if (m_parent->m_onConnected) m_parent->m_onConnected(sessionId, state, info); } - void OnDisconnected(unsigned sessionId, Hermes::EState state, const Hermes::Error& error) override { - if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error); + void OnDisconnected(unsigned sessionId, EState state, const Error& error) override + { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(sessionId, state, error); } - void OnState(unsigned sessionId, Hermes::EState state) override { - if (m_parent->m_onStateChange) m_parent->m_onStateChange(state); + void OnState(unsigned sessionId, EState state) override + { + if (m_parent->m_onState) m_parent->m_onState(sessionId, state); } - void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { - if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); + void OnTrace(unsigned sessionId, ETraceType type, StringView trace) override + { + if (m_parent->m_onTrace) m_parent->m_onTrace(sessionId, type, trace); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::ServiceDescriptionData& data) override { - if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(data); + // Core messages received from the upstream machine: + void On(unsigned sessionId, EState state, const ServiceDescriptionData& data) override + { + if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(sessionId, state, data); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::MachineReadyData& data) override { - if (m_parent->m_onMachineReady) m_parent->m_onMachineReady(data); + void On(unsigned sessionId, EState state, const MachineReadyData& data) override + { + if (m_parent->m_onMachineReady) m_parent->m_onMachineReady(sessionId, state, data); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::RevokeMachineReadyData& data) override { - if (m_parent->m_onRevokeMachineReady) m_parent->m_onRevokeMachineReady(data); + void On(unsigned sessionId, EState state, const RevokeMachineReadyData& data) override + { + if (m_parent->m_onRevokeMachineReady) m_parent->m_onRevokeMachineReady(sessionId, state, data); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::StartTransportData& data) override { - if (m_parent->m_onStartTransport) m_parent->m_onStartTransport(data); + void On(unsigned sessionId, EState state, const StartTransportData& data) override + { + if (m_parent->m_onStartTransport) m_parent->m_onStartTransport(sessionId, state, data); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::StopTransportData& data) override { - if (m_parent->m_onStopTransport) m_parent->m_onStopTransport(data); + void On(unsigned sessionId, EState state, const StopTransportData& data) override + { + if (m_parent->m_onStopTransport) m_parent->m_onStopTransport(sessionId, state, data); } - void On(unsigned sessionId, const Hermes::QueryBoardInfoData& data) override { - if (m_parent->m_onQueryBoardInfo) m_parent->m_onQueryBoardInfo(data); + + // Optional messages (non-abstract in IDownstreamCallback): + void On(unsigned sessionId, const QueryBoardInfoData& data) override + { + if (m_parent->m_onQueryBoardInfo) m_parent->m_onQueryBoardInfo(sessionId, data); } - void On(unsigned sessionId, const Hermes::NotificationData& data) override { - if (m_parent->m_onNotification) m_parent->m_onNotification(data); + void On(unsigned sessionId, const NotificationData& data) override + { + if (m_parent->m_onNotification) m_parent->m_onNotification(sessionId, data); } - void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { - if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); + void On(unsigned sessionId, const CheckAliveData& data) override + { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(sessionId, data); } - void On(unsigned sessionId, const Hermes::CommandData& data) override { - if (m_parent->m_onCommand) m_parent->m_onCommand(data); + void On(unsigned sessionId, const CommandData& data) override + { + if (m_parent->m_onCommand) m_parent->m_onCommand(sessionId, data); } + + private: + Downstream* m_parent; }; - unsigned m_laneId; - std::atomic m_isRunning; - std::thread m_networkThread; - std::unique_ptr m_callbackWrapper; + unsigned m_laneId; + std::atomic m_isRunning{false}; + std::thread m_networkThread; + std::unique_ptr m_callbackWrapper; std::unique_ptr m_downstream; - ConnectedCallback m_onConnected; - DisconnectedCallback m_onDisconnected; - StateChangeCallback m_onStateChange; - TraceCallback m_onTrace; - - ServiceDescriptionCallback m_onServiceDescription; - MachineReadyCallback m_onMachineReady; - RevokeMachineReadyCallback m_onRevokeMachineReady; - StartTransportCallback m_onStartTransport; - StopTransportCallback m_onStopTransport; - QueryBoardInfoCallback m_onQueryBoardInfo; - - NotificationCallback m_onNotification; - CheckAliveCallback m_onCheckAlive; - CommandCallback m_onCommand; + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + StateCallback m_onState; + TraceCallback m_onTrace; + ServiceDescriptionCallback m_onServiceDescription; + MachineReadyCallback m_onMachineReady; + RevokeMachineReadyCallback m_onRevokeMachineReady; + StartTransportCallback m_onStartTransport; + StopTransportCallback m_onStopTransport; + QueryBoardInfoCallback m_onQueryBoardInfo; + NotificationCallback m_onNotification; + CheckAliveCallback m_onCheckAlive; + CommandCallback m_onCommand; }; -// ============================================================================== -// Upstream Wrapper (Sender - Horizontal) -// ============================================================================== -class Upstream { + +// ============================================================================= +// Upstream +// +// Wraps Hermes::Upstream (horizontal lane, upstream port). +// The upstream side *receives* messages sent by the downstream machine: +// ServiceDescriptionData, BoardAvailableData, RevokeBoardAvailableData, +// TransportFinishedData, BoardForecastData (optional), SendBoardInfoData (optional), +// NotificationData, CheckAliveData (optional), CommandData. +// +// The upstream side *sends*: +// ServiceDescriptionData, MachineReadyData, RevokeMachineReadyData, +// StartTransportData, StopTransportData, QueryBoardInfoData, +// NotificationData, CheckAliveData, CommandData. +// ============================================================================= +class Upstream +{ public: - using ConnectedCallback = std::function; - using DisconnectedCallback = std::function; - using StateChangeCallback = std::function; - using TraceCallback = std::function; - - using ServiceDescriptionCallback = std::function; - using BoardAvailableCallback = std::function; - using RevokeBoardAvailableCallback = std::function; - using TransportFinishedCallback = std::function; - using BoardForecastCallback = std::function; - using SendBoardInfoCallback = std::function; - - using NotificationCallback = std::function; - using CheckAliveCallback = std::function; - using CommandCallback = std::function; - - Upstream(unsigned laneId) : m_laneId(laneId), m_isRunning(false) { - m_callbackWrapper = std::make_unique(this); - m_upstream = std::make_unique(laneId, *m_callbackWrapper); + // ---- Connection lifecycle callbacks ---- + + /// Called when a TCP connection is established with the downstream machine. + using ConnectedCallback = std::function; + /// Called when the TCP connection is dropped. + using DisconnectedCallback = std::function; + /// Called on every protocol state machine transition. + using StateCallback = std::function; + /// Called for every internal trace/log event. + using TraceCallback = std::function; + + // ---- Received message callbacks (sent by the downstream machine) ---- + + /// Received: downstream declares its service capabilities. + using ServiceDescriptionCallback = std::function; + /// Received: downstream signals a board is available for transfer. + using BoardAvailableCallback = std::function; + /// Received: downstream revokes a previously sent BoardAvailable. + using RevokeBoardAvailableCallback = std::function; + /// Received: downstream signals the board transfer is complete. + using TransportFinishedCallback = std::function; + /// Received: downstream forecasts an upcoming board (optional message). + using BoardForecastCallback = std::function; + /// Received: downstream sends board information in response to QueryBoardInfo (optional message). + using SendBoardInfoCallback = std::function; + /// Received: downstream sends a notification. + using NotificationCallback = std::function; + /// Received: downstream sends a check-alive ping/pong (optional message). + using CheckAliveCallback = std::function; + /// Received: downstream sends a command. + using CommandCallback = std::function; + + /// @param laneId The lane number this upstream port belongs to (1-based). + explicit Upstream(unsigned laneId) + : m_laneId(laneId) + { + m_callbackWrapper = std::make_unique(this); + m_upstream = std::make_unique(laneId, *m_callbackWrapper); } ~Upstream() { Stop(); } - void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } - void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } - void RegisterStateChangeCallback(StateChangeCallback cb) { m_onStateChange = cb; } - void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } - - void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb) { m_onServiceDescription = cb; } - void RegisterBoardAvailableCallback(BoardAvailableCallback cb) { m_onBoardAvailable = cb; } - void RegisterRevokeBoardAvailableCallback(RevokeBoardAvailableCallback cb) { m_onRevokeBoardAvailable = cb; } - void RegisterTransportFinishedCallback(TransportFinishedCallback cb) { m_onTransportFinished = cb; } - void RegisterBoardForecastCallback(BoardForecastCallback cb) { m_onBoardForecast = cb; } - void RegisterSendBoardInfoCallback(SendBoardInfoCallback cb) { m_onSendBoardInfo = cb; } - void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } - void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } - void RegisterCommandCallback(CommandCallback cb) { m_onCommand = cb; } - - void Enable(const UpstreamSettings& settings) { - if (m_isRunning) return; - m_upstream->Enable(settings); - m_isRunning = true; - m_networkThread = std::thread([this]() { m_upstream->Run(); }); + Upstream(const Upstream&) = delete; + Upstream& operator=(const Upstream&) = delete; + + // ---- Callback registration ---- + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = std::move(cb); } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = std::move(cb); } + void RegisterStateCallback(StateCallback cb) { m_onState = std::move(cb); } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = std::move(cb); } + void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb) { m_onServiceDescription = std::move(cb); } + void RegisterBoardAvailableCallback(BoardAvailableCallback cb) { m_onBoardAvailable = std::move(cb); } + void RegisterRevokeBoardAvailableCallback(RevokeBoardAvailableCallback cb) { m_onRevokeBoardAvailable = std::move(cb); } + void RegisterTransportFinishedCallback(TransportFinishedCallback cb) { m_onTransportFinished = std::move(cb); } + void RegisterBoardForecastCallback(BoardForecastCallback cb) { m_onBoardForecast = std::move(cb); } + void RegisterSendBoardInfoCallback(SendBoardInfoCallback cb) { m_onSendBoardInfo = std::move(cb); } + void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = std::move(cb); } + void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = std::move(cb); } + void RegisterCommandCallback(CommandCallback cb) { m_onCommand = std::move(cb); } + + // ---- Lifecycle ---- + + /// Start the IO loop and enable the upstream port. + /// Enable() is posted into the IO thread to guarantee it executes after Run() starts. + void Enable(const UpstreamSettings& settings) + { + if (m_isRunning.exchange(true)) + return; + + m_networkThread = std::thread([this, settings]() + { + m_upstream->Post([this, settings]() + { + m_upstream->Enable(settings); + }); + m_upstream->Run(); + }); } - void Stop() { - if (m_isRunning) { - m_upstream->Stop(); - if (m_networkThread.joinable()) m_networkThread.join(); - m_isRunning = false; - } + /// Stop the IO loop and join the thread. Safe to call multiple times. + void Stop() + { + if (!m_isRunning.exchange(false)) + return; + m_upstream->Stop(); + if (m_networkThread.joinable()) + m_networkThread.join(); } + // ---- Sending messages ---- + + /// Send a message to the connected downstream machine. template void Signal(unsigned sessionId, const T& data) { m_upstream->Signal(sessionId, data); } + /// Post a callable into the IO thread. + template + void Post(F&& f) { m_upstream->Post(std::forward(f)); } + + /// Reset all sessions with a notification. + void Reset(const NotificationData& data) { m_upstream->Reset(data); } + + /// Disable the port gracefully. + void Disable(const NotificationData& data) { m_upstream->Disable(data); } + private: - class InternalCallbackWrapper : public Hermes::IUpstreamCallback { - Upstream* m_parent; + class CallbackWrapper : public Hermes::IUpstreamCallback + { public: - InternalCallbackWrapper(Upstream* parent) : m_parent(parent) {} + explicit CallbackWrapper(Upstream* parent) : m_parent(parent) {} - void OnConnected(unsigned sessionId, Hermes::EState state, const Hermes::ConnectionInfo& info) override { - if (m_parent->m_onConnected) m_parent->m_onConnected(info); + void OnConnected(unsigned sessionId, EState state, const ConnectionInfo& info) override + { + if (m_parent->m_onConnected) m_parent->m_onConnected(sessionId, state, info); } - void OnDisconnected(unsigned sessionId, Hermes::EState state, const Hermes::Error& error) override { - if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error); + void OnDisconnected(unsigned sessionId, EState state, const Error& error) override + { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(sessionId, state, error); } - void OnState(unsigned sessionId, Hermes::EState state) override { - if (m_parent->m_onStateChange) m_parent->m_onStateChange(state); + void OnState(unsigned sessionId, EState state) override + { + if (m_parent->m_onState) m_parent->m_onState(sessionId, state); } - void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { - if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); + void OnTrace(unsigned sessionId, ETraceType type, StringView trace) override + { + if (m_parent->m_onTrace) m_parent->m_onTrace(sessionId, type, trace); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::ServiceDescriptionData& data) override { - if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(data); + // Core messages received from the downstream machine: + void On(unsigned sessionId, EState state, const ServiceDescriptionData& data) override + { + if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(sessionId, state, data); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::BoardAvailableData& data) override { - if (m_parent->m_onBoardAvailable) m_parent->m_onBoardAvailable(data); + void On(unsigned sessionId, EState state, const BoardAvailableData& data) override + { + if (m_parent->m_onBoardAvailable) m_parent->m_onBoardAvailable(sessionId, state, data); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::RevokeBoardAvailableData& data) override { - if (m_parent->m_onRevokeBoardAvailable) m_parent->m_onRevokeBoardAvailable(data); + void On(unsigned sessionId, EState state, const RevokeBoardAvailableData& data) override + { + if (m_parent->m_onRevokeBoardAvailable) m_parent->m_onRevokeBoardAvailable(sessionId, state, data); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::TransportFinishedData& data) override { - if (m_parent->m_onTransportFinished) m_parent->m_onTransportFinished(data); + void On(unsigned sessionId, EState state, const TransportFinishedData& data) override + { + if (m_parent->m_onTransportFinished) m_parent->m_onTransportFinished(sessionId, state, data); } - void On(unsigned sessionId, Hermes::EState state, const Hermes::BoardForecastData& data) override { - if (m_parent->m_onBoardForecast) m_parent->m_onBoardForecast(data); + + // Optional messages (non-abstract in IUpstreamCallback): + void On(unsigned sessionId, EState state, const BoardForecastData& data) override + { + if (m_parent->m_onBoardForecast) m_parent->m_onBoardForecast(sessionId, state, data); } - void On(unsigned sessionId, const Hermes::SendBoardInfoData& data) override { - if (m_parent->m_onSendBoardInfo) m_parent->m_onSendBoardInfo(data); + void On(unsigned sessionId, const SendBoardInfoData& data) override + { + if (m_parent->m_onSendBoardInfo) m_parent->m_onSendBoardInfo(sessionId, data); } - void On(unsigned sessionId, const Hermes::NotificationData& data) override { - if (m_parent->m_onNotification) m_parent->m_onNotification(data); + void On(unsigned sessionId, const NotificationData& data) override + { + if (m_parent->m_onNotification) m_parent->m_onNotification(sessionId, data); } - void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { - if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); + void On(unsigned sessionId, const CheckAliveData& data) override + { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(sessionId, data); } - void On(unsigned sessionId, const Hermes::CommandData& data) override { - if (m_parent->m_onCommand) m_parent->m_onCommand(data); + void On(unsigned sessionId, const CommandData& data) override + { + if (m_parent->m_onCommand) m_parent->m_onCommand(sessionId, data); } + + private: + Upstream* m_parent; }; - unsigned m_laneId; - std::atomic m_isRunning; - std::thread m_networkThread; - std::unique_ptr m_callbackWrapper; + unsigned m_laneId; + std::atomic m_isRunning{false}; + std::thread m_networkThread; + std::unique_ptr m_callbackWrapper; std::unique_ptr m_upstream; - ConnectedCallback m_onConnected; - DisconnectedCallback m_onDisconnected; - StateChangeCallback m_onStateChange; - TraceCallback m_onTrace; - - ServiceDescriptionCallback m_onServiceDescription; - BoardAvailableCallback m_onBoardAvailable; + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + StateCallback m_onState; + TraceCallback m_onTrace; + ServiceDescriptionCallback m_onServiceDescription; + BoardAvailableCallback m_onBoardAvailable; RevokeBoardAvailableCallback m_onRevokeBoardAvailable; - TransportFinishedCallback m_onTransportFinished; - BoardForecastCallback m_onBoardForecast; - SendBoardInfoCallback m_onSendBoardInfo; - - NotificationCallback m_onNotification; - CheckAliveCallback m_onCheckAlive; - CommandCallback m_onCommand; + TransportFinishedCallback m_onTransportFinished; + BoardForecastCallback m_onBoardForecast; + SendBoardInfoCallback m_onSendBoardInfo; + NotificationCallback m_onNotification; + CheckAliveCallback m_onCheckAlive; + CommandCallback m_onCommand; }; -// ============================================================================== -// VerticalService Wrapper (Machine Server - Vertical) -// ============================================================================== -class VerticalService { +// ============================================================================= +// VerticalService +// +// Wraps Hermes::VerticalService (vertical supervisory interface, server side). +// Receives from the vertical client: +// SupervisoryServiceDescriptionData, GetConfigurationData, SetConfigurationData, +// SendWorkOrderInfoData (optional), QueryHermesCapabilitiesData, +// NotificationData, CheckAliveData (optional). +// +// Sends to the vertical client: +// SupervisoryServiceDescriptionData, BoardArrivedData, BoardDepartedData, +// QueryWorkOrderInfoData, ReplyWorkOrderInfoData, SendHermesCapabilitiesData, +// CurrentConfigurationData, NotificationData, CheckAliveData, CommandData. +// ============================================================================= +class VerticalService +{ public: - using ConnectedCallback = std::function; - using DisconnectedCallback = std::function; - using TraceCallback = std::function; - - using SupervisoryServiceDescriptionCallback = std::function; - using GetConfigurationCallback = std::function; - using SetConfigurationCallback = std::function; - using SendWorkOrderInfoCallback = std::function; - using QueryHermesCapabilitiesCallback = std::function; - - using NotificationCallback = std::function; - using CheckAliveCallback = std::function; - - VerticalService() : m_isRunning(false) { - m_callbackWrapper = std::make_unique(this); - m_verticalService = std::make_unique(*m_callbackWrapper); + // ---- Connection lifecycle callbacks ---- + using ConnectedCallback = std::function; + using DisconnectedCallback = std::function; + using TraceCallback = std::function; + + // ---- Received message callbacks ---- + + /// Received: client declares its supervisory service capabilities. + using ServiceDescriptionCallback = std::function; + /// Received: client requests the current configuration. + using GetConfigurationCallback = std::function; + /// Received: client sets a new configuration. + using SetConfigurationCallback = std::function; + /// Received: client sends work order information (optional message). + using SendWorkOrderInfoCallback = std::function; + /// Received: client sends a notification. + using NotificationCallback = std::function; + /// Received: client queries Hermes capabilities. + using QueryHermesCapabilitiesCallback = std::function; + /// Received: client sends a check-alive (optional message). + using CheckAliveCallback = std::function; + + explicit VerticalService() + { + m_callbackWrapper = std::make_unique(this); + m_service = std::make_unique(*m_callbackWrapper); } ~VerticalService() { Stop(); } - void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } - void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } - void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } - - void RegisterSupervisoryServiceDescriptionCallback(SupervisoryServiceDescriptionCallback cb) { m_onSupervisoryServiceDescription = cb; } - void RegisterGetConfigurationCallback(GetConfigurationCallback cb) { m_onGetConfiguration = cb; } - void RegisterSetConfigurationCallback(SetConfigurationCallback cb) { m_onSetConfiguration = cb; } - void RegisterSendWorkOrderInfoCallback(SendWorkOrderInfoCallback cb) { m_onSendWorkOrderInfo = cb; } - void RegisterQueryHermesCapabilitiesCallback(QueryHermesCapabilitiesCallback cb) { m_onQueryHermesCapabilities = cb; } - void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } - void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } - - void Enable(const VerticalServiceSettings& settings) { - if (m_isRunning) return; - m_verticalService->Enable(settings); - m_isRunning = true; - m_networkThread = std::thread([this]() { m_verticalService->Run(); }); + VerticalService(const VerticalService&) = delete; + VerticalService& operator=(const VerticalService&) = delete; + + // ---- Callback registration ---- + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = std::move(cb); } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = std::move(cb); } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = std::move(cb); } + void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb) { m_onServiceDescription = std::move(cb); } + void RegisterGetConfigurationCallback(GetConfigurationCallback cb) { m_onGetConfiguration = std::move(cb); } + void RegisterSetConfigurationCallback(SetConfigurationCallback cb) { m_onSetConfiguration = std::move(cb); } + void RegisterSendWorkOrderInfoCallback(SendWorkOrderInfoCallback cb) { m_onSendWorkOrderInfo = std::move(cb); } + void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = std::move(cb); } + void RegisterQueryHermesCapabilitiesCallback(QueryHermesCapabilitiesCallback cb) { m_onQueryHermesCapabilities = std::move(cb); } + void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = std::move(cb); } + + // ---- Lifecycle ---- + + /// Start the IO loop and enable the vertical service. + void Enable(const VerticalServiceSettings& settings) + { + if (m_isRunning.exchange(true)) + return; + + m_networkThread = std::thread([this, settings]() + { + m_service->Post([this, settings]() + { + m_service->Enable(settings); + }); + m_service->Run(); + }); } - void Stop() { - if (m_isRunning) { - m_verticalService->Stop(); - if (m_networkThread.joinable()) m_networkThread.join(); - m_isRunning = false; - } + /// Stop the IO loop and join the thread. Safe to call multiple times. + void Stop() + { + if (!m_isRunning.exchange(false)) + return; + m_service->Stop(); + if (m_networkThread.joinable()) + m_networkThread.join(); } + // ---- Sending messages ---- + template - void Signal(unsigned sessionId, const T& data) { m_verticalService->Signal(sessionId, data); } - - template - void SignalBroadcast(const T& data) { m_verticalService->Signal(data); } + void Signal(unsigned sessionId, const T& data) { m_service->Signal(sessionId, data); } + + /// Broadcast BoardArrivedData to all clients that requested FeatureBoardTracking. + void Signal(const BoardArrivedData& data) { m_service->Signal(data); } + /// Broadcast BoardDepartedData to all clients that requested FeatureBoardTracking. + void Signal(const BoardDepartedData& data) { m_service->Signal(data); } + + template + void Post(F&& f) { m_service->Post(std::forward(f)); } + + void ResetSession(unsigned sessionId, const NotificationData& data) { m_service->ResetSession(sessionId, data); } + void Disable(const NotificationData& data) { m_service->Disable(data); } private: - class InternalCallbackWrapper : public Hermes::IVerticalServiceCallback { - VerticalService* m_parent; + class CallbackWrapper : public Hermes::IVerticalServiceCallback + { public: - InternalCallbackWrapper(VerticalService* parent) : m_parent(parent) {} + explicit CallbackWrapper(VerticalService* parent) : m_parent(parent) {} - void OnConnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::ConnectionInfo& info) override { - if (m_parent->m_onConnected) m_parent->m_onConnected(info, state); + void OnConnected(unsigned sessionId, EVerticalState state, const ConnectionInfo& info) override + { + if (m_parent->m_onConnected) m_parent->m_onConnected(sessionId, state, info); } - void OnDisconnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::Error& error) override { - if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error, state); + void OnDisconnected(unsigned sessionId, EVerticalState state, const Error& error) override + { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(sessionId, state, error); } - void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { - if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); + void OnTrace(unsigned sessionId, ETraceType type, StringView trace) override + { + if (m_parent->m_onTrace) m_parent->m_onTrace(sessionId, type, trace); } - - void On(unsigned sessionId, Hermes::EVerticalState state, const Hermes::SupervisoryServiceDescriptionData& data) override { - if (m_parent->m_onSupervisoryServiceDescription) m_parent->m_onSupervisoryServiceDescription(data, state); + void On(unsigned sessionId, EVerticalState state, const SupervisoryServiceDescriptionData& data) override + { + if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(sessionId, state, data); } - void On(unsigned sessionId, const Hermes::GetConfigurationData& data, const Hermes::ConnectionInfo& info) override { - if (m_parent->m_onGetConfiguration) m_parent->m_onGetConfiguration(data, info); + void On(unsigned sessionId, const GetConfigurationData& data, const ConnectionInfo& info) override + { + if (m_parent->m_onGetConfiguration) m_parent->m_onGetConfiguration(sessionId, data, info); } - void On(unsigned sessionId, const Hermes::SetConfigurationData& data, const Hermes::ConnectionInfo& info) override { - if (m_parent->m_onSetConfiguration) m_parent->m_onSetConfiguration(data, info); + void On(unsigned sessionId, const SetConfigurationData& data, const ConnectionInfo& info) override + { + if (m_parent->m_onSetConfiguration) m_parent->m_onSetConfiguration(sessionId, data, info); } - void On(unsigned sessionId, const Hermes::SendWorkOrderInfoData& data) override { - if (m_parent->m_onSendWorkOrderInfo) m_parent->m_onSendWorkOrderInfo(data); + void On(unsigned sessionId, const SendWorkOrderInfoData& data) override + { + if (m_parent->m_onSendWorkOrderInfo) m_parent->m_onSendWorkOrderInfo(sessionId, data); } - void On(unsigned sessionId, const Hermes::QueryHermesCapabilitiesData& data) override { - if (m_parent->m_onQueryHermesCapabilities) m_parent->m_onQueryHermesCapabilities(data); + void On(unsigned sessionId, const NotificationData& data) override + { + if (m_parent->m_onNotification) m_parent->m_onNotification(sessionId, data); } - void On(unsigned sessionId, const Hermes::NotificationData& data) override { - if (m_parent->m_onNotification) m_parent->m_onNotification(data); + void On(unsigned sessionId, const QueryHermesCapabilitiesData& data) override + { + if (m_parent->m_onQueryHermesCapabilities) m_parent->m_onQueryHermesCapabilities(sessionId, data); } - void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { - if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); + void On(unsigned sessionId, const CheckAliveData& data) override + { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(sessionId, data); } + + private: + VerticalService* m_parent; }; - std::atomic m_isRunning; - std::thread m_networkThread; - std::unique_ptr m_callbackWrapper; - std::unique_ptr m_verticalService; - - ConnectedCallback m_onConnected; - DisconnectedCallback m_onDisconnected; - TraceCallback m_onTrace; - - SupervisoryServiceDescriptionCallback m_onSupervisoryServiceDescription; - GetConfigurationCallback m_onGetConfiguration; - SetConfigurationCallback m_onSetConfiguration; - SendWorkOrderInfoCallback m_onSendWorkOrderInfo; - QueryHermesCapabilitiesCallback m_onQueryHermesCapabilities; - NotificationCallback m_onNotification; - CheckAliveCallback m_onCheckAlive; + std::atomic m_isRunning{false}; + std::thread m_networkThread; + std::unique_ptr m_callbackWrapper; + std::unique_ptr m_service; + + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + TraceCallback m_onTrace; + ServiceDescriptionCallback m_onServiceDescription; + GetConfigurationCallback m_onGetConfiguration; + SetConfigurationCallback m_onSetConfiguration; + SendWorkOrderInfoCallback m_onSendWorkOrderInfo; + NotificationCallback m_onNotification; + QueryHermesCapabilitiesCallback m_onQueryHermesCapabilities; + CheckAliveCallback m_onCheckAlive; }; -// ============================================================================== -// VerticalClient Wrapper (MES/Factory Cloud Client - Vertical) -// ============================================================================== -class VerticalClient { +// ============================================================================= +// VerticalClient +// +// Wraps Hermes::VerticalClient (vertical supervisory interface, client side). +// Receives from the vertical service: +// SupervisoryServiceDescriptionData, BoardArrivedData (optional), +// BoardDepartedData (optional), QueryWorkOrderInfoData, ReplyWorkOrderInfoData, +// CurrentConfigurationData (optional), SendHermesCapabilitiesData (optional), +// NotificationData, CheckAliveData (optional). +// +// Sends to the vertical service: +// SupervisoryServiceDescriptionData, GetConfigurationData, SetConfigurationData, +// QueryHermesCapabilitiesData, SendWorkOrderInfoData, +// NotificationData, CheckAliveData. +// ============================================================================= +class VerticalClient +{ public: - using ConnectedCallback = std::function; - using DisconnectedCallback = std::function; - using TraceCallback = std::function; - - using SupervisoryServiceDescriptionCallback = std::function; - using BoardArrivedCallback = std::function; - using BoardDepartedCallback = std::function; - using QueryWorkOrderInfoCallback = std::function; - using ReplyWorkOrderInfoCallback = std::function; - using CurrentConfigurationCallback = std::function; - using SendHermesCapabilitiesCallback = std::function; - - using NotificationCallback = std::function; - using CheckAliveCallback = std::function; - - VerticalClient() : m_isRunning(false) { - m_callbackWrapper = std::make_unique(this); - m_verticalClient = std::make_unique(*m_callbackWrapper); + // ---- Connection lifecycle callbacks ---- + using ConnectedCallback = std::function; + using DisconnectedCallback = std::function; + using TraceCallback = std::function; + + // ---- Received message callbacks ---- + + /// Received: service declares its supervisory service capabilities. + using ServiceDescriptionCallback = std::function; + /// Received: service notifies a board has arrived at a machine (optional message). + using BoardArrivedCallback = std::function; + /// Received: service notifies a board has departed a machine (optional message). + using BoardDepartedCallback = std::function; + /// Received: service queries work order information. + using QueryWorkOrderInfoCallback = std::function; + /// Received: service replies to a SendWorkOrderInfo request. + using ReplyWorkOrderInfoCallback = std::function; + /// Received: service sends the current configuration (optional message). + using CurrentConfigurationCallback = std::function; + /// Received: service sends its Hermes capabilities (optional message). + using SendHermesCapabilitiesCallback = std::function; + /// Received: service sends a notification. + using NotificationCallback = std::function; + /// Received: service sends a check-alive (optional message). + using CheckAliveCallback = std::function; + + explicit VerticalClient() + { + m_callbackWrapper = std::make_unique(this); + m_client = std::make_unique(*m_callbackWrapper); } ~VerticalClient() { Stop(); } - void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = cb; } - void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = cb; } - void RegisterTraceCallback(TraceCallback cb) { m_onTrace = cb; } - - void RegisterSupervisoryServiceDescriptionCallback(SupervisoryServiceDescriptionCallback cb) { m_onSupervisoryServiceDescription = cb; } - void RegisterBoardArrivedCallback(BoardArrivedCallback cb) { m_onBoardArrived = cb; } - void RegisterBoardDepartedCallback(BoardDepartedCallback cb) { m_onBoardDeparted = cb; } - void RegisterQueryWorkOrderInfoCallback(QueryWorkOrderInfoCallback cb) { m_onQueryWorkOrderInfo = cb; } - void RegisterReplyWorkOrderInfoCallback(ReplyWorkOrderInfoCallback cb) { m_onReplyWorkOrderInfo = cb; } - void RegisterCurrentConfigurationCallback(CurrentConfigurationCallback cb) { m_onCurrentConfiguration = cb; } - void RegisterSendHermesCapabilitiesCallback(SendHermesCapabilitiesCallback cb) { m_onSendHermesCapabilities = cb; } - void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = cb; } - void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = cb; } - - void Enable(const VerticalClientSettings& settings) { - if (m_isRunning) return; - m_verticalClient->Enable(settings); - m_isRunning = true; - m_networkThread = std::thread([this]() { m_verticalClient->Run(); }); + VerticalClient(const VerticalClient&) = delete; + VerticalClient& operator=(const VerticalClient&) = delete; + + // ---- Callback registration ---- + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = std::move(cb); } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = std::move(cb); } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = std::move(cb); } + void RegisterServiceDescriptionCallback(ServiceDescriptionCallback cb) { m_onServiceDescription = std::move(cb); } + void RegisterBoardArrivedCallback(BoardArrivedCallback cb) { m_onBoardArrived = std::move(cb); } + void RegisterBoardDepartedCallback(BoardDepartedCallback cb) { m_onBoardDeparted = std::move(cb); } + void RegisterQueryWorkOrderInfoCallback(QueryWorkOrderInfoCallback cb) { m_onQueryWorkOrderInfo = std::move(cb); } + void RegisterReplyWorkOrderInfoCallback(ReplyWorkOrderInfoCallback cb) { m_onReplyWorkOrderInfo = std::move(cb); } + void RegisterCurrentConfigurationCallback(CurrentConfigurationCallback cb) { m_onCurrentConfiguration = std::move(cb); } + void RegisterSendHermesCapabilitiesCallback(SendHermesCapabilitiesCallback cb) { m_onSendHermesCapabilities = std::move(cb); } + void RegisterNotificationCallback(NotificationCallback cb) { m_onNotification = std::move(cb); } + void RegisterCheckAliveCallback(CheckAliveCallback cb) { m_onCheckAlive = std::move(cb); } + + // ---- Lifecycle ---- + + /// Start the IO loop and enable the vertical client. + void Enable(const VerticalClientSettings& settings) + { + if (m_isRunning.exchange(true)) + return; + + m_networkThread = std::thread([this, settings]() + { + m_client->Post([this, settings]() + { + m_client->Enable(settings); + }); + m_client->Run(); + }); } - void Stop() { - if (m_isRunning) { - m_verticalClient->Stop(); - if (m_networkThread.joinable()) m_networkThread.join(); - m_isRunning = false; - } + /// Stop the IO loop and join the thread. Safe to call multiple times. + void Stop() + { + if (!m_isRunning.exchange(false)) + return; + m_client->Stop(); + if (m_networkThread.joinable()) + m_networkThread.join(); } + // ---- Sending messages ---- + template - void Signal(unsigned sessionId, const T& data) { m_verticalClient->Signal(sessionId, data); } + void Signal(unsigned sessionId, const T& data) { m_client->Signal(sessionId, data); } + + template + void Post(F&& f) { m_client->Post(std::forward(f)); } + + void Reset(const NotificationData& data) { m_client->Reset(data); } + void Disable(const NotificationData& data) { m_client->Disable(data); } private: - class InternalCallbackWrapper : public Hermes::IVerticalClientCallback { - VerticalClient* m_parent; + class CallbackWrapper : public Hermes::IVerticalClientCallback + { public: - InternalCallbackWrapper(VerticalClient* parent) : m_parent(parent) {} + explicit CallbackWrapper(VerticalClient* parent) : m_parent(parent) {} - void OnConnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::ConnectionInfo& info) override { - if (m_parent->m_onConnected) m_parent->m_onConnected(info, state); + void OnConnected(unsigned sessionId, EVerticalState state, const ConnectionInfo& info) override + { + if (m_parent->m_onConnected) m_parent->m_onConnected(sessionId, state, info); } - void OnDisconnected(unsigned sessionId, Hermes::EVerticalState state, const Hermes::Error& error) override { - if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(error, state); + void OnDisconnected(unsigned sessionId, EVerticalState state, const Error& error) override + { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(sessionId, state, error); } - void OnTrace(unsigned sessionId, Hermes::ETraceType type, Hermes::StringView trace) override { - if (m_parent->m_onTrace) m_parent->m_onTrace(type, trace); + void OnTrace(unsigned sessionId, ETraceType type, StringView trace) override + { + if (m_parent->m_onTrace) m_parent->m_onTrace(sessionId, type, trace); } - - void On(unsigned sessionId, Hermes::EVerticalState state, const Hermes::SupervisoryServiceDescriptionData& data) override { - if (m_parent->m_onSupervisoryServiceDescription) m_parent->m_onSupervisoryServiceDescription(data, state); + void On(unsigned sessionId, EVerticalState state, const SupervisoryServiceDescriptionData& data) override + { + if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(sessionId, state, data); } - void On(unsigned sessionId, const Hermes::BoardArrivedData& data) override { - if (m_parent->m_onBoardArrived) m_parent->m_onBoardArrived(data); + void On(unsigned sessionId, const BoardArrivedData& data) override + { + if (m_parent->m_onBoardArrived) m_parent->m_onBoardArrived(sessionId, data); } - void On(unsigned sessionId, const Hermes::BoardDepartedData& data) override { - if (m_parent->m_onBoardDeparted) m_parent->m_onBoardDeparted(data); + void On(unsigned sessionId, const BoardDepartedData& data) override + { + if (m_parent->m_onBoardDeparted) m_parent->m_onBoardDeparted(sessionId, data); } - void On(unsigned sessionId, const Hermes::QueryWorkOrderInfoData& data) override { - if (m_parent->m_onQueryWorkOrderInfo) m_parent->m_onQueryWorkOrderInfo(data); + void On(unsigned sessionId, const QueryWorkOrderInfoData& data) override + { + if (m_parent->m_onQueryWorkOrderInfo) m_parent->m_onQueryWorkOrderInfo(sessionId, data); } - void On(unsigned sessionId, const Hermes::ReplyWorkOrderInfoData& data) override { - if (m_parent->m_onReplyWorkOrderInfo) m_parent->m_onReplyWorkOrderInfo(data); + void On(unsigned sessionId, const ReplyWorkOrderInfoData& data) override + { + if (m_parent->m_onReplyWorkOrderInfo) m_parent->m_onReplyWorkOrderInfo(sessionId, data); } - void On(unsigned sessionId, const Hermes::CurrentConfigurationData& data) override { - if (m_parent->m_onCurrentConfiguration) m_parent->m_onCurrentConfiguration(data); + void On(unsigned sessionId, const CurrentConfigurationData& data) override + { + if (m_parent->m_onCurrentConfiguration) m_parent->m_onCurrentConfiguration(sessionId, data); } - void On(unsigned sessionId, const Hermes::SendHermesCapabilitiesData& data) override { - if (m_parent->m_onSendHermesCapabilities) m_parent->m_onSendHermesCapabilities(data); + void On(unsigned sessionId, const SendHermesCapabilitiesData& data) override + { + if (m_parent->m_onSendHermesCapabilities) m_parent->m_onSendHermesCapabilities(sessionId, data); } - void On(unsigned sessionId, const Hermes::NotificationData& data) override { - if (m_parent->m_onNotification) m_parent->m_onNotification(data); + void On(unsigned sessionId, const NotificationData& data) override + { + if (m_parent->m_onNotification) m_parent->m_onNotification(sessionId, data); } - void On(unsigned sessionId, const Hermes::CheckAliveData& data) override { - if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(data); + void On(unsigned sessionId, const CheckAliveData& data) override + { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(sessionId, data); } + + private: + VerticalClient* m_parent; }; - std::atomic m_isRunning; - std::thread m_networkThread; - std::unique_ptr m_callbackWrapper; - std::unique_ptr m_verticalClient; - - ConnectedCallback m_onConnected; - DisconnectedCallback m_onDisconnected; - TraceCallback m_onTrace; - - SupervisoryServiceDescriptionCallback m_onSupervisoryServiceDescription; - BoardArrivedCallback m_onBoardArrived; - BoardDepartedCallback m_onBoardDeparted; - QueryWorkOrderInfoCallback m_onQueryWorkOrderInfo; - ReplyWorkOrderInfoCallback m_onReplyWorkOrderInfo; - CurrentConfigurationCallback m_onCurrentConfiguration; - SendHermesCapabilitiesCallback m_onSendHermesCapabilities; - NotificationCallback m_onNotification; - CheckAliveCallback m_onCheckAlive; + std::atomic m_isRunning{false}; + std::thread m_networkThread; + std::unique_ptr m_callbackWrapper; + std::unique_ptr m_client; + + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + TraceCallback m_onTrace; + ServiceDescriptionCallback m_onServiceDescription; + BoardArrivedCallback m_onBoardArrived; + BoardDepartedCallback m_onBoardDeparted; + QueryWorkOrderInfoCallback m_onQueryWorkOrderInfo; + ReplyWorkOrderInfoCallback m_onReplyWorkOrderInfo; + CurrentConfigurationCallback m_onCurrentConfiguration; + SendHermesCapabilitiesCallback m_onSendHermesCapabilities; + NotificationCallback m_onNotification; + CheckAliveCallback m_onCheckAlive; +}; + + +// ============================================================================= +// ConfigurationService +// +// Wraps Hermes::ConfigurationService. +// Handles GetConfiguration and SetConfiguration requests from remote clients. +// +// Note: Unlike the horizontal/vertical wrappers, OnGetConfiguration and +// OnSetConfiguration have return values (they are query/response patterns), +// so they use std::function with return types rather than void callbacks. +// ============================================================================= +class ConfigurationService +{ +public: + using ConnectedCallback = std::function; + using DisconnectedCallback = std::function; + using TraceCallback = std::function; + + /// Called when a remote client requests the current configuration. + /// Must return a CurrentConfigurationData. Required. + using GetConfigurationCallback = std::function; + + /// Called when a remote client pushes a new configuration. + /// Must return an Error (return Error{} for success). Required. + using SetConfigurationCallback = std::function; + + explicit ConfigurationService() + { + m_callbackWrapper = std::make_unique(this); + m_service = std::make_unique(*m_callbackWrapper); + } + + ~ConfigurationService() { Stop(); } + + ConfigurationService(const ConfigurationService&) = delete; + ConfigurationService& operator=(const ConfigurationService&) = delete; + + // ---- Callback registration ---- + void RegisterConnectedCallback(ConnectedCallback cb) { m_onConnected = std::move(cb); } + void RegisterDisconnectedCallback(DisconnectedCallback cb) { m_onDisconnected = std::move(cb); } + void RegisterTraceCallback(TraceCallback cb) { m_onTrace = std::move(cb); } + + /// Register the get-configuration handler. This is required; the service + /// cannot respond to GetConfiguration requests without it. + void RegisterGetConfigurationCallback(GetConfigurationCallback cb) { m_onGetConfiguration = std::move(cb); } + + /// Register the set-configuration handler. This is required; the service + /// cannot process SetConfiguration requests without it. + void RegisterSetConfigurationCallback(SetConfigurationCallback cb) { m_onSetConfiguration = std::move(cb); } + + // ---- Lifecycle ---- + + void Enable(const ConfigurationServiceSettings& settings) + { + if (m_isRunning.exchange(true)) + return; + + m_networkThread = std::thread([this, settings]() + { + m_service->Post([this, settings]() + { + m_service->Enable(settings); + }); + m_service->Run(); + }); + } + + void Stop() + { + if (!m_isRunning.exchange(false)) + return; + m_service->Stop(); + if (m_networkThread.joinable()) + m_networkThread.join(); + } + + template + void Post(F&& f) { m_service->Post(std::forward(f)); } + + void Disable(const NotificationData& data) { m_service->Disable(data); } + +private: + class CallbackWrapper : public Hermes::IConfigurationServiceCallback + { + public: + explicit CallbackWrapper(ConfigurationService* parent) : m_parent(parent) {} + + void OnConnected(unsigned sessionId, const ConnectionInfo& info) override + { + if (m_parent->m_onConnected) m_parent->m_onConnected(sessionId, info); + } + void OnDisconnected(unsigned sessionId, const Error& error) override + { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(sessionId, error); + } + void OnTrace(unsigned sessionId, ETraceType type, StringView trace) override + { + if (m_parent->m_onTrace) m_parent->m_onTrace(sessionId, type, trace); + } + CurrentConfigurationData OnGetConfiguration(unsigned sessionId, const ConnectionInfo& info) override + { + if (m_parent->m_onGetConfiguration) + return m_parent->m_onGetConfiguration(sessionId, info); + return CurrentConfigurationData{}; + } + Error OnSetConfiguration(unsigned sessionId, const ConnectionInfo& info, const SetConfigurationData& data) override + { + if (m_parent->m_onSetConfiguration) + return m_parent->m_onSetConfiguration(sessionId, info, data); + return Error{}; + } + + private: + ConfigurationService* m_parent; + }; + + std::atomic m_isRunning{false}; + std::thread m_networkThread; + std::unique_ptr m_callbackWrapper; + std::unique_ptr m_service; + + ConnectedCallback m_onConnected; + DisconnectedCallback m_onDisconnected; + TraceCallback m_onTrace; + GetConfigurationCallback m_onGetConfiguration; + SetConfigurationCallback m_onSetConfiguration; }; } // namespace Modern