Skip to content

Commit 181dd8e

Browse files
authored
Creation of modbus tcp client and state machine (#699)
* Creation of modbus tcp client and state machine * Fixing Pre-commit format error
1 parent d233a71 commit 181dd8e

File tree

31 files changed

+3482
-0
lines changed

31 files changed

+3482
-0
lines changed

docs/cl_modbus_tcp_relay_plan.md

Lines changed: 820 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
cmake_minimum_required(VERSION 3.5)
2+
project(cl_modbus_tcp_relay)
3+
4+
# Default to C++17
5+
if(NOT CMAKE_CXX_STANDARD)
6+
set(CMAKE_CXX_STANDARD 17)
7+
endif()
8+
9+
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
10+
add_compile_options(-Wall -Wextra -Wpedantic)
11+
endif()
12+
13+
# find dependencies
14+
find_package(ament_cmake REQUIRED)
15+
find_package(rclcpp REQUIRED)
16+
find_package(smacc2 REQUIRED)
17+
find_package(PkgConfig REQUIRED)
18+
19+
# Find libmodbus - prefer system-installed, fallback to workspace source
20+
pkg_check_modules(MODBUS libmodbus)
21+
22+
if(NOT MODBUS_FOUND)
23+
# Check if libmodbus source exists in workspace
24+
set(LIBMODBUS_WORKSPACE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../../libmodbus/src")
25+
if(EXISTS "${LIBMODBUS_WORKSPACE_PATH}/modbus.h")
26+
message(STATUS "Using workspace libmodbus source at: ${LIBMODBUS_WORKSPACE_PATH}")
27+
set(MODBUS_INCLUDE_DIRS ${LIBMODBUS_WORKSPACE_PATH})
28+
# Note: When using workspace source, libmodbus must be built separately
29+
# For full functionality, install libmodbus-dev: sudo apt install libmodbus-dev
30+
set(MODBUS_LIBRARIES "modbus")
31+
set(MODBUS_FOUND TRUE)
32+
else()
33+
message(FATAL_ERROR "libmodbus not found. Install with: sudo apt install libmodbus-dev")
34+
endif()
35+
endif()
36+
37+
set(dependencies
38+
rclcpp
39+
smacc2
40+
)
41+
42+
include_directories(
43+
include
44+
${MODBUS_INCLUDE_DIRS}
45+
)
46+
47+
add_library(${PROJECT_NAME} SHARED
48+
src/${PROJECT_NAME}/cl_modbus_tcp_relay.cpp
49+
src/${PROJECT_NAME}/components/cp_modbus_connection.cpp
50+
src/${PROJECT_NAME}/components/cp_modbus_relay.cpp
51+
)
52+
53+
target_link_libraries(${PROJECT_NAME}
54+
${MODBUS_LIBRARIES}
55+
)
56+
57+
ament_target_dependencies(${PROJECT_NAME}
58+
${dependencies}
59+
)
60+
61+
ament_export_include_directories(include)
62+
ament_export_libraries(${PROJECT_NAME})
63+
ament_export_dependencies(${dependencies})
64+
65+
install(TARGETS ${PROJECT_NAME}
66+
ARCHIVE DESTINATION lib
67+
LIBRARY DESTINATION lib
68+
RUNTIME DESTINATION bin
69+
)
70+
71+
install(DIRECTORY include/
72+
DESTINATION include/
73+
)
74+
75+
ament_package()
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# cl_modbus_tcp_relay
2+
3+
SMACC2 client library for controlling Modbus TCP relay boards, specifically designed for the Waveshare 8-Channel POE ETH Relay.
4+
5+
## Overview
6+
7+
This client library provides a SMACC2-compatible interface for controlling relay boards via Modbus TCP protocol. It uses the libmodbus C library for communication and follows SMACC2's pure component-based architecture.
8+
9+
## Target Hardware
10+
11+
- **Device**: Waveshare 8-Channel POE ETH Relay (or compatible)
12+
- **Protocol**: Modbus TCP
13+
- **Default IP**: 192.168.1.254
14+
- **Default Port**: 502
15+
- **Slave ID**: 0x01
16+
- **Coil Addresses**: 0x0000-0x0007 (channels 1-8)
17+
18+
## Dependencies
19+
20+
### System Dependencies
21+
22+
```bash
23+
sudo apt install libmodbus-dev
24+
```
25+
26+
### ROS2 Dependencies
27+
28+
- smacc2
29+
30+
## Architecture
31+
32+
### Client: ClModbusTcpRelay
33+
34+
Pure orchestrator that creates and configures components during initialization. Configuration is loaded from YAML parameters.
35+
36+
### Components
37+
38+
#### CpModbusConnection
39+
40+
Manages libmodbus context lifecycle, TCP connection, and heartbeat monitoring.
41+
42+
**Responsibilities:**
43+
- Create/destroy modbus_t context
44+
- Manage TCP connection state
45+
- Periodic heartbeat via ISmaccUpdatable
46+
- Emit connection state change signals
47+
- Thread-safe connection access via mutex
48+
49+
**Signals:**
50+
- `onConnectionLost_` - Emitted when heartbeat fails
51+
- `onConnectionRestored_` - Emitted when reconnection succeeds
52+
- `onConnectionError_` - Emitted on connection errors
53+
54+
#### CpModbusRelay
55+
56+
Handles Modbus coil read/write operations for the 8-channel relay.
57+
58+
**Methods:**
59+
- `writeCoil(int channel, bool state)` - Write single channel (1-8)
60+
- `writeAllCoils(bool state)` - Write all channels ON or OFF
61+
- `writeAllCoils(uint8_t mask)` - Write all channels with bitmask
62+
- `readCoil(int channel)` - Read single channel state
63+
- `readAllCoils()` - Read all channel states
64+
65+
### Client Behaviors
66+
67+
| Behavior | Description |
68+
|----------|-------------|
69+
| `CbRelayOn` | Turn on a specific relay channel (1-8) |
70+
| `CbRelayOff` | Turn off a specific relay channel (1-8) |
71+
| `CbAllRelaysOn` | Turn on all 8 relay channels |
72+
| `CbAllRelaysOff` | Turn off all 8 relay channels |
73+
| `CbRelayStatus` | Read the status of relay channels |
74+
75+
### Events
76+
77+
```cpp
78+
// Connection events (source: CpModbusConnection)
79+
EvConnectionLost<CpModbusConnection, OrRelay>
80+
EvConnectionRestored<CpModbusConnection, OrRelay>
81+
82+
// Relay operation events (source: CpModbusRelay)
83+
EvRelayWriteSuccess<CpModbusRelay, OrRelay>
84+
EvRelayWriteFailure<CpModbusRelay, OrRelay>
85+
```
86+
87+
## Configuration
88+
89+
Configuration is loaded from ROS2 parameters (typically via YAML config file):
90+
91+
```yaml
92+
your_state_machine:
93+
ros__parameters:
94+
modbus_relay:
95+
ip_address: "192.168.1.254" # Relay board IP address
96+
port: 502 # Modbus TCP port
97+
slave_id: 1 # Modbus slave ID
98+
heartbeat_interval_ms: 1000 # Heartbeat check interval
99+
connect_on_init: true # Auto-connect on initialization
100+
```
101+
102+
### Default Values
103+
104+
| Parameter | Default |
105+
|-----------|---------|
106+
| `ip_address` | "192.168.1.254" |
107+
| `port` | 502 |
108+
| `slave_id` | 1 |
109+
| `heartbeat_interval_ms` | 1000 |
110+
| `connect_on_init` | true |
111+
112+
## Usage
113+
114+
### Orthogonal Definition
115+
116+
```cpp
117+
#include <cl_modbus_tcp_relay/cl_modbus_tcp_relay.hpp>
118+
119+
class OrRelay : public smacc2::Orthogonal<OrRelay>
120+
{
121+
public:
122+
void onInitialize() override
123+
{
124+
// Configuration loaded from YAML parameters
125+
auto relay_client = this->createClient<cl_modbus_tcp_relay::ClModbusTcpRelay>();
126+
}
127+
};
128+
```
129+
130+
### State Definition
131+
132+
```cpp
133+
#include <cl_modbus_tcp_relay/client_behaviors/cb_relay_on.hpp>
134+
#include <cl_modbus_tcp_relay/client_behaviors/cb_relay_off.hpp>
135+
136+
struct StActivateRelay : smacc2::SmaccState<StActivateRelay, SmExample>
137+
{
138+
using SmaccState::SmaccState;
139+
140+
typedef mpl::list<
141+
Transition<EvCbSuccess<CbRelayOn, OrRelay>, StNextState>,
142+
Transition<EvCbFailure<CbRelayOn, OrRelay>, StError>,
143+
Transition<EvConnectionLost<CpModbusConnection, OrRelay>, StReconnect>
144+
> reactions;
145+
146+
static void staticConfigure()
147+
{
148+
// Turn on channel 1
149+
configure_orthogonal<OrRelay, cl_modbus_tcp_relay::CbRelayOn>(1);
150+
}
151+
};
152+
```
153+
154+
### Launch File
155+
156+
```python
157+
from launch import LaunchDescription
158+
from launch_ros.actions import Node
159+
from ament_index_python.packages import get_package_share_directory
160+
import os
161+
162+
def generate_launch_description():
163+
config_file = os.path.join(
164+
get_package_share_directory('your_state_machine'),
165+
'config',
166+
'your_config.yaml'
167+
)
168+
169+
return LaunchDescription([
170+
Node(
171+
package='your_state_machine',
172+
executable='your_state_machine_node',
173+
name='your_state_machine',
174+
output='screen',
175+
parameters=[config_file]
176+
)
177+
])
178+
```
179+
180+
## Manual Testing with mbpoll
181+
182+
For debugging and manual testing, you can use the `mbpoll` command-line tool:
183+
184+
```bash
185+
# Install mbpoll
186+
sudo apt install mbpoll
187+
188+
# Read all 8 coil states
189+
mbpoll -m tcp -a 1 -t 0 -r 1 -c 8 192.168.1.254
190+
191+
# Write single coil ON (channel 1)
192+
mbpoll -m tcp -a 1 -t 0 -r 1 192.168.1.254 1
193+
194+
# Write single coil OFF (channel 1)
195+
mbpoll -m tcp -a 1 -t 0 -r 1 192.168.1.254 0
196+
197+
# Write all coils ON
198+
mbpoll -m tcp -a 1 -t 0 -r 1 -c 8 192.168.1.254 1 1 1 1 1 1 1 1
199+
200+
# Write all coils OFF
201+
mbpoll -m tcp -a 1 -t 0 -r 1 -c 8 192.168.1.254 0 0 0 0 0 0 0 0
202+
```
203+
204+
### mbpoll Options Reference
205+
206+
| Option | Description |
207+
|--------|-------------|
208+
| `-m tcp` | Use Modbus TCP protocol |
209+
| `-a 1` | Slave address (1) |
210+
| `-t 0` | Data type: coil (0) |
211+
| `-r 1` | Starting reference (1 = address 0x0000) |
212+
| `-c 8` | Number of coils to read/write |
213+
214+
## Modbus Protocol Reference
215+
216+
| Operation | Function Code | Address Range |
217+
|-----------|---------------|---------------|
218+
| Read Coils | 0x01 | 0x0000-0x0007 |
219+
| Write Single Coil | 0x05 | 0x0000-0x0007 |
220+
| Write Multiple Coils | 0x0F | 0x0000 (8 coils) |
221+
222+
### Coil Values
223+
224+
- ON: 0xFF00 (function 0x05) or 1 (function 0x0F)
225+
- OFF: 0x0000
226+
227+
## Building
228+
229+
```bash
230+
# From workspace root
231+
colcon build --packages-select cl_modbus_tcp_relay
232+
```
233+
234+
## Test State Machine
235+
236+
See `sm_modbus_tcp_relay_test_1` for a complete test state machine that exercises all behaviors.
237+
238+
## License
239+
240+
Apache-2.0

0 commit comments

Comments
 (0)