From d7049fb8b2d63235a02114f7c657d5b726d15a6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Sandstr=C3=B6m?= Date: Fri, 23 Oct 2026 10:26:45 +0200 Subject: [PATCH] WIP --- MTEST04-MTN-HEXAPOD_parameters.yaml | 7 ++++ MTEST04-MTN-HEXAPOD_startup.script | 20 +++++++++ Makefile | 64 +++++++++++------------------ README.md | 14 ++++++- cfg/axis_coordinate.yaml | 2 +- cfg/axis_leg.yaml | 2 +- cfg/load.cmd | 2 +- old_Makefile | 43 +++++++++++++++++++ src/Makefile | 24 +++++++++++ src/logic.cpp | 5 ++- 10 files changed, 136 insertions(+), 47 deletions(-) create mode 100644 MTEST04-MTN-HEXAPOD_parameters.yaml create mode 100644 MTEST04-MTN-HEXAPOD_startup.script create mode 100644 old_Makefile create mode 100644 src/Makefile diff --git a/MTEST04-MTN-HEXAPOD_parameters.yaml b/MTEST04-MTN-HEXAPOD_parameters.yaml new file mode 100644 index 0000000..1ba6d39 --- /dev/null +++ b/MTEST04-MTN-HEXAPOD_parameters.yaml @@ -0,0 +1,7 @@ +ioc_name: MTEST04-MTN-HEXAPOD +ioc_host: c6025a-08 +ioc_port: 50001 +ioc_user: root +epics_version: 7.0.10 +os: deb12 +cpu_architecture: x86_64 diff --git a/MTEST04-MTN-HEXAPOD_startup.script b/MTEST04-MTN-HEXAPOD_startup.script new file mode 100644 index 0000000..4c2edac --- /dev/null +++ b/MTEST04-MTN-HEXAPOD_startup.script @@ -0,0 +1,20 @@ +# Hexapod cpp_logic simulation IOC example. +# Run with the hexapod project as the IOC working directory. +# Build first: make logic ECMC_ROOT= ECMCCFG_ROOT= +# build/libhexapod_logic.so +# cfg/libhexapod_logic.so_cpp_logic.subs +# +# Axes 1..6: X, Y, Z, Rx, Ry, Rz (internal coordinate trajectories). +# Axes 7..12: L1..L6 (external IK trajectories and ideal leg feedback). +# No EtherCAT master or physical hardware is required. +# The IOC launcher supplies iocInit; ecmccfg finalizes at that point. +# +# Useful panels after startup: +# caqtdm -macro "IOC=HEXAPOD" ecmcCppLogicOverview.ui +# caqtdm -macro "IOC=HEXAPOD,CPP_ID=0" ecmcCppLogic.ui + +epicsEnvSet("IOC", "${IOC=HEXAPD}") +require ecmccfg v11.2.0_RC1 "ECMC_VER=v11.2.0_RC1,MASTER_ID=-1,EC_RATE=1000" + +# Load all twelve virtual axes, then the C++ transform and its status PVs. +${SCRIPTEXEC} ./cfg/load.cmd diff --git a/Makefile b/Makefile index e58e13e..83aa811 100644 --- a/Makefile +++ b/Makefile @@ -1,43 +1,27 @@ -CXX ?= c++ -CXXFLAGS ?= -O2 -std=c++17 -Wall -Wextra -pedantic -ECMC_ROOT ?= ../ecmc -ECMCCFG_ROOT ?= ../ecmccfg -PYTHON ?= python3 -UNAME := $(shell uname -s) -ifeq ($(UNAME),Darwin) -SHARED := -dynamiclib -EXT := dylib -else -SHARED := -shared -EXT := so +include /ioc/tools/sls_ioc.makefile + +# The offline build also creates a build/ directory. Always run the IOC +# build recipe so the source Makefile can check its own dependencies. +.PHONY: default build clean + +IOC_STARTUP_FILE := $(notdir $(firstword $(wildcard *_startup.script))) +IOC_PARAMS_FILE := $(notdir $(firstword $(wildcard *_parameters.yaml))) +IOC_NAME ?= $(patsubst %_startup.script,%,$(IOC_STARTUP_FILE)) +ifeq ($(IOC_NAME),) +IOC_NAME := $(patsubst %_parameters.yaml,%,$(IOC_PARAMS_FILE)) endif -.PHONY: all test logic pvs clean -all: build/libhexapod.$(EXT) build/test_kinematics -build: - mkdir -p build -build/libhexapod.$(EXT): src/c_api.cpp src/kinematics.hpp | build - $(CXX) $(CXXFLAGS) -fPIC $(SHARED) -o $@ src/c_api.cpp -build/test_kinematics: tests/test_kinematics.cpp src/kinematics.hpp | build - $(CXX) $(CXXFLAGS) -Isrc -o $@ tests/test_kinematics.cpp -test: all - ./build/test_kinematics - $(PYTHON) python/viewer.py --self-test -logic: build/libhexapod_logic.$(EXT) pvs -build/libhexapod_logic.$(EXT): src/logic.cpp src/kinematics.hpp | build - $(CXX) $(CXXFLAGS) -fPIC $(SHARED) -I$(ECMC_ROOT)/devEcmcSup/logic -o $@ src/logic.cpp + +CPP_LOGIC_ROOT ?= $(abspath ../..) +PYTHON ?= python3 +GENERATE_QT ?= 1 +IOC_QT_PANEL := qt/$(IOC_NAME)_cpp_logic.ui +IOC_QTGEN := $(CPP_LOGIC_ROOT)/utils/cpp_logic_ioc_qtgen.py +IOC_SUBS_FILE := cfg/libmain.so_cpp_logic.subs + +.PHONY: pvs ui + pvs: - $(PYTHON) $(ECMCCFG_ROOT)/examples/PSI/plugins/cpp_logic/utils/ecmcCppLogicSourceSubstGen.py --source src/logic.cpp --logic-name hexapod --output cfg/libhexapod_logic.so_cpp_logic.subs -clean: - rm -rf build + $(MAKE) -C src stage -.PHONY: test-logic -test-logic: build/test_logic - ./build/test_logic -build/test_logic: tests/test_logic.cpp src/logic.cpp src/kinematics.hpp | build - $(CXX) $(CXXFLAGS) -I$(ECMC_ROOT)/devEcmcSup/logic -o $@ tests/test_logic.cpp - -.PHONY: bench -bench: build/kinematics_bench - ./build/kinematics_bench -build/kinematics_bench: bench/kinematics_bench.cpp src/kinematics.hpp | build - $(CXX) $(CXXFLAGS) -Isrc -o $@ bench/kinematics_bench.cpp +ui: pvs + if [ "$(GENERATE_QT)" = "1" ]; then $(PYTHON) $(IOC_QTGEN) --input $(IOC_SUBS_FILE) --output $(IOC_QT_PANEL) --pv-prefix "$(IOC_NAME):" --title "$(IOC_NAME) cpp_logic PVs"; fi diff --git a/README.md b/README.md index a406ae8..e174458 100644 --- a/README.md +++ b/README.md @@ -303,7 +303,17 @@ This produces `build/libhexapod_logic.so` and the generated EPICS substitutions. On macOS the build produces `.dylib` files for development tests; these cannot be loaded in a Linux IOC. -From an existing ecmccfg IOC startup, with this project as its working directory, +`HEXAPD_startup.script` is the simulation IOC startup, following the ecmccfg +`cpp_logic_motion` example. It requires `ecmccfg strucpp` with `ECMC_VER=strucpp`, +selects a 1 kHz cycle without an EtherCAT master, and loads all twelve virtual +axes and the C++ logic through `cfg/load.cmd`. Build the library first using +headers matching that ecmc installation, then run `HEXAPD_startup.script` with your +normal PSI IOC launcher and this project as its working directory. The launcher +supplies `iocInit`; ecmccfg finalizes the configuration and enters runtime during +initialization. The default IOC name is `HEXAPD` unless the launcher sets `IOC`. +Axes remain disabled at startup and motor records are not loaded. + +To integrate into an existing ecmccfg IOC startup, with this project as its working directory, include the following **after ecmccfg initialization and before runtime startup**: ```text @@ -346,7 +356,7 @@ prefix, including the trailing colon: ```sh .venv/bin/pip install -r python/requirements-ioc.txt -.venv/bin/python python/viewer.py --prefix HEX: +.venv/bin/python python/viewer.py --prefix HEXAPD: ``` Use the prefix configured by `loadCppLogic.cmd` (`DB_PREFIX`, default `${IOC}:`). diff --git a/cfg/axis_coordinate.yaml b/cfg/axis_coordinate.yaml index 8575051..44ac258 100644 --- a/cfg/axis_coordinate.yaml +++ b/cfg/axis_coordinate.yaml @@ -20,7 +20,7 @@ encoder: numerator: 1 denominator: 1 bits: 32 - position: ec0.s-1.ONE + # External feedback is supplied by C++ FK; no EtherCAT position link. trajectory: source: 0 diff --git a/cfg/axis_leg.yaml b/cfg/axis_leg.yaml index 2d43980..419947b 100644 --- a/cfg/axis_leg.yaml +++ b/cfg/axis_leg.yaml @@ -21,7 +21,7 @@ encoder: numerator: 1 denominator: 1 bits: 32 - position: ec0.s-1.ONE + # External feedback is supplied by the C++ ideal plant; no EtherCAT position link. trajectory: source: 1 diff --git a/cfg/load.cmd b/cfg/load.cmd index d349e4b..d0f9c1a 100644 --- a/cfg/load.cmd +++ b/cfg/load.cmd @@ -3,4 +3,4 @@ # cfg/axes.cmd loads the two shared axis YAML templates via loadYamlAxis.cmd. # Axis IDs 1..12 must be free. No physical EtherCAT hardware is linked. ${SCRIPTEXEC} ./cfg/axes.cmd -${SCRIPTEXEC} ${ecmccfg_DIR}loadCppLogic.cmd, "DIR=./build/,FILE=libhexapod_logic.so,ASYN_PORT=HEX,REPORT=1,MACROS='COORD_FIRST=1,LEG_FIRST=7,SIMULATION=1'" +${SCRIPTEXEC} ${ecmccfg_DIR}loadCppLogic.cmd, "ASYN_PORT=HEX,REPORT=1,MACROS='COORD_FIRST=1,LEG_FIRST=7,SIMULATION=1'" diff --git a/old_Makefile b/old_Makefile new file mode 100644 index 0000000..e58e13e --- /dev/null +++ b/old_Makefile @@ -0,0 +1,43 @@ +CXX ?= c++ +CXXFLAGS ?= -O2 -std=c++17 -Wall -Wextra -pedantic +ECMC_ROOT ?= ../ecmc +ECMCCFG_ROOT ?= ../ecmccfg +PYTHON ?= python3 +UNAME := $(shell uname -s) +ifeq ($(UNAME),Darwin) +SHARED := -dynamiclib +EXT := dylib +else +SHARED := -shared +EXT := so +endif +.PHONY: all test logic pvs clean +all: build/libhexapod.$(EXT) build/test_kinematics +build: + mkdir -p build +build/libhexapod.$(EXT): src/c_api.cpp src/kinematics.hpp | build + $(CXX) $(CXXFLAGS) -fPIC $(SHARED) -o $@ src/c_api.cpp +build/test_kinematics: tests/test_kinematics.cpp src/kinematics.hpp | build + $(CXX) $(CXXFLAGS) -Isrc -o $@ tests/test_kinematics.cpp +test: all + ./build/test_kinematics + $(PYTHON) python/viewer.py --self-test +logic: build/libhexapod_logic.$(EXT) pvs +build/libhexapod_logic.$(EXT): src/logic.cpp src/kinematics.hpp | build + $(CXX) $(CXXFLAGS) -fPIC $(SHARED) -I$(ECMC_ROOT)/devEcmcSup/logic -o $@ src/logic.cpp +pvs: + $(PYTHON) $(ECMCCFG_ROOT)/examples/PSI/plugins/cpp_logic/utils/ecmcCppLogicSourceSubstGen.py --source src/logic.cpp --logic-name hexapod --output cfg/libhexapod_logic.so_cpp_logic.subs +clean: + rm -rf build + +.PHONY: test-logic +test-logic: build/test_logic + ./build/test_logic +build/test_logic: tests/test_logic.cpp src/logic.cpp src/kinematics.hpp | build + $(CXX) $(CXXFLAGS) -I$(ECMC_ROOT)/devEcmcSup/logic -o $@ tests/test_logic.cpp + +.PHONY: bench +bench: build/kinematics_bench + ./build/kinematics_bench +build/kinematics_bench: bench/kinematics_bench.cpp src/kinematics.hpp | build + $(CXX) $(CXXFLAGS) -Isrc -o $@ bench/kinematics_bench.cpp diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..8bfb285 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,24 @@ +include /ioc/tools/driver.makefile +MODULE = main +BUILDCLASSES = Linux +ARCH_FILTER = deb10% deb12% +EXCLUDE_VERSIONS+=3 7.0.5 7.0.6 7.0.7 +USR_CXXFLAGS += -std=c++17 +ecmc_VERSION = v11.2.0_RC1 +DIRS+=. +SOURCES += $(foreach d,${DIRS}, $(wildcard $d/*.c) $(wildcard $d/*.cpp)) +HEADERS += $(foreach d,${DIRS}, $(wildcard $d/*.h)) +HEADERS += $(foreach d,${DIRS}, $(wildcard $d/*.hpp)) + +CPP_LOGIC_ROOT ?= $(abspath ../../..) +PYTHON ?= python3.9 +SUBSGEN := $(CPP_LOGIC_ROOT)/utils/ecmcCppLogicSourceSubstGen.py +STAGE_SUBS ?= ../cfg/lib$(MODULE).so_cpp_logic.subs +STAGE_DIR := $(dir $(STAGE_SUBS)) +LOCAL_MODULE := $(notdir $(abspath ..)) + +.PHONY: stage + +stage: + mkdir -p $(STAGE_DIR); \ + $(PYTHON) $(SUBSGEN) --source main.cpp --logic-name "$(LOCAL_MODULE)" --output $(STAGE_SUBS) diff --git a/src/logic.cpp b/src/logic.cpp index 75255ea..8cf2856 100644 --- a/src/logic.cpp +++ b/src/logic.cpp @@ -37,8 +37,9 @@ struct HexapodLogic : ecmcCpp::LogicBase { model=hexapod::Model(g); geometry_home=model.inverse({}); if(!model.withinStroke(geometry_home)) throw std::runtime_error("Home is outside leg stroke"); for(int i=0;i<6;++i) { - ecmc.input("ax"+std::to_string(coordFirst+i)+".traj.setpos",pose_set[i]); - ecmc.input("ax"+std::to_string(legFirst+i)+".enc.actpos",leg_actual[i]); + // C++ item bindings use asyn names, rather than PLC object paths. + ecmc.input("ax"+std::to_string(coordFirst+i)+".setpos",pose_set[i]); + ecmc.input("ax"+std::to_string(legFirst+i)+".actpos",leg_actual[i]); } epics.readOnlyArray("hex.pose_set",pose_set) .readOnlyArray("hex.pose_actual",pose_actual)