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..69607d9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +# ============================================================================== +# 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!) +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) diff --git a/README.md b/README.md index 3438c14..6a1e653 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,217 @@ -# Hermes +# Hermes C++ Library — Complete Reference -"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. +**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+ -# Contents +--- -The GIT-repository contains following directories: +## Table of Contents -- 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) +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` | + +--- +# 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: + +* **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 +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. + +- 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). + +- 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 +``` + +#### Step 2: Configure the Project + +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 .. +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: + +- Linux: libhermes.so + +- 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 my_app my_app.cpp \ + -I//src/include \ + -L//build/src/Hermes -lhermes \ + -lboost_system -lpthread +``` +**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: + +- 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 + +| 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/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/examples/rpi1_upstream.cpp b/examples/rpi1_upstream.cpp new file mode 100644 index 0000000..95c6110 --- /dev/null +++ b/examples/rpi1_upstream.cpp @@ -0,0 +1,121 @@ +#include "Hermes.hpp" +#include +#include +#include +#include +#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; +} + +struct UpstreamCallback : Hermes::IUpstreamCallback { + Hermes::Upstream* m_pUpstream = nullptr; + + 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); + }); + } + + 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"; + }); + } + + 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 OnState(unsigned, Hermes::EState s) override { + g_currentStateInt = static_cast(s); + std::cout << "[RPi1] State Transition: " << g_currentStateInt << "\n"; + } + + void OnDisconnected(unsigned, Hermes::EState, const Hermes::Error& e) override { + g_activeSession = 0; std::cout << "[RPi1] Disconnected: " << e.m_text << "\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 {} +}; + +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); + callback.m_pUpstream = &upstream; + + Hermes::UpstreamSettings settings; + settings.m_machineId = "RPi1_Upstream"; + settings.m_hostAddress = "10.0.0.2"; + settings.m_port = 50100; + + upstream.Enable(settings); + 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 (netThread.joinable()) netThread.join(); + return 0; +} diff --git a/examples/rpi2_downstream.cpp b/examples/rpi2_downstream.cpp new file mode 100644 index 0000000..0ffe777 --- /dev/null +++ b/examples/rpi2_downstream.cpp @@ -0,0 +1,123 @@ +#include "Hermes.hpp" +#include +#include +#include +#include +#include +#include +#include + +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); + } +} + +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; +} + +struct DownstreamCallback : Hermes::IDownstreamCallback { + Hermes::Downstream* m_pDownstream = nullptr; + + void OnConnected(unsigned sessionId, Hermes::EState, const Hermes::ConnectionInfo&) override { + g_activeSession = sessionId; + std::cout << "\n[RPi2] CONNECTION ESTABLISHED.\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); + }); + } + + void OnState(unsigned, Hermes::EState s) override { + g_currentStateInt = static_cast(s); + std::cout << "[RPi2] State Transition: " << g_currentStateInt << "\n"; + } + + void On(unsigned, Hermes::EState, const Hermes::StartTransportData&) override { + std::cout << "[RPi2] === TRANSPORT ENGAGED (Motors ON) ===\n"; + } + + void OnDisconnected(unsigned, Hermes::EState, const Hermes::Error& e) override { + g_activeSession = 0; std::cout << "[RPi2] Disconnected: " << e.m_text << "\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, [](int) { g_running = false; }); + set_raw_mode(true); + + DownstreamCallback callback; + Hermes::Downstream downstream(1, callback); + callback.m_pDownstream = &downstream; + + Hermes::DownstreamSettings settings; + settings.m_machineId = "RPi2_Downstream"; + settings.m_port = 50100; + + downstream.Enable(settings); + 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 (netThread.joinable()) netThread.join(); + return 0; +} 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.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/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/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..88ed21e --- /dev/null +++ b/src/include/HermesModern.hpp @@ -0,0 +1,980 @@ +/*********************************************************************** +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 + +namespace Hermes { +namespace Modern { + +// ============================================================================= +// 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: + // ---- 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(); } + + 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(); + }); + } + + /// 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: + // Bridges Hermes::IDownstreamCallback pure virtuals to our std::function members. + class CallbackWrapper : public Hermes::IDownstreamCallback + { + public: + explicit CallbackWrapper(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, state, info); + } + 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, EState state) override + { + if (m_parent->m_onState) m_parent->m_onState(sessionId, state); + } + void OnTrace(unsigned sessionId, ETraceType type, StringView trace) override + { + if (m_parent->m_onTrace) m_parent->m_onTrace(sessionId, type, trace); + } + + // 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, EState state, const MachineReadyData& data) override + { + if (m_parent->m_onMachineReady) m_parent->m_onMachineReady(sessionId, state, 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, EState state, const StartTransportData& data) override + { + if (m_parent->m_onStartTransport) m_parent->m_onStartTransport(sessionId, state, data); + } + void On(unsigned sessionId, EState state, const StopTransportData& data) override + { + if (m_parent->m_onStopTransport) m_parent->m_onStopTransport(sessionId, state, 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 NotificationData& data) override + { + if (m_parent->m_onNotification) m_parent->m_onNotification(sessionId, 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 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{false}; + std::thread m_networkThread; + std::unique_ptr m_callbackWrapper; + std::unique_ptr m_downstream; + + 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 +// +// 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: + // ---- 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(); } + + 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(); + }); + } + + /// 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 CallbackWrapper : public Hermes::IUpstreamCallback + { + public: + explicit CallbackWrapper(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, state, info); + } + 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, EState state) override + { + if (m_parent->m_onState) m_parent->m_onState(sessionId, state); + } + void OnTrace(unsigned sessionId, ETraceType type, StringView trace) override + { + if (m_parent->m_onTrace) m_parent->m_onTrace(sessionId, type, trace); + } + + // 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, EState state, const BoardAvailableData& data) override + { + if (m_parent->m_onBoardAvailable) m_parent->m_onBoardAvailable(sessionId, state, 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, EState state, const TransportFinishedData& data) override + { + if (m_parent->m_onTransportFinished) m_parent->m_onTransportFinished(sessionId, state, 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 SendBoardInfoData& data) override + { + if (m_parent->m_onSendBoardInfo) m_parent->m_onSendBoardInfo(sessionId, 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 CheckAliveData& data) override + { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(sessionId, 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{false}; + std::thread m_networkThread; + std::unique_ptr m_callbackWrapper; + std::unique_ptr m_upstream; + + 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; +}; + + +// ============================================================================= +// 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: + // ---- 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(); } + + 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(); + }); + } + + /// 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_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 CallbackWrapper : public Hermes::IVerticalServiceCallback + { + public: + explicit CallbackWrapper(VerticalService* parent) : m_parent(parent) {} + + 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, EVerticalState state, const Error& error) override + { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(sessionId, state, error); + } + 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, EVerticalState state, const SupervisoryServiceDescriptionData& data) override + { + if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(sessionId, state, data); + } + 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 SetConfigurationData& data, const ConnectionInfo& info) override + { + if (m_parent->m_onSetConfiguration) m_parent->m_onSetConfiguration(sessionId, data, info); + } + void On(unsigned sessionId, const SendWorkOrderInfoData& data) override + { + if (m_parent->m_onSendWorkOrderInfo) m_parent->m_onSendWorkOrderInfo(sessionId, 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 QueryHermesCapabilitiesData& data) override + { + if (m_parent->m_onQueryHermesCapabilities) m_parent->m_onQueryHermesCapabilities(sessionId, 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{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 +// +// 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: + // ---- 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(); } + + 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(); + }); + } + + /// 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_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 CallbackWrapper : public Hermes::IVerticalClientCallback + { + public: + explicit CallbackWrapper(VerticalClient* parent) : m_parent(parent) {} + + 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, EVerticalState state, const Error& error) override + { + if (m_parent->m_onDisconnected) m_parent->m_onDisconnected(sessionId, state, error); + } + 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, EVerticalState state, const SupervisoryServiceDescriptionData& data) override + { + if (m_parent->m_onServiceDescription) m_parent->m_onServiceDescription(sessionId, state, 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 BoardDepartedData& data) override + { + if (m_parent->m_onBoardDeparted) m_parent->m_onBoardDeparted(sessionId, 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 ReplyWorkOrderInfoData& data) override + { + if (m_parent->m_onReplyWorkOrderInfo) m_parent->m_onReplyWorkOrderInfo(sessionId, 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 SendHermesCapabilitiesData& data) override + { + if (m_parent->m_onSendHermesCapabilities) m_parent->m_onSendHermesCapabilities(sessionId, 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 CheckAliveData& data) override + { + if (m_parent->m_onCheckAlive) m_parent->m_onCheckAlive(sessionId, data); + } + + private: + VerticalClient* m_parent; + }; + + 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 +} // 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) - -