SMACC2
Loading...
Searching...
No Matches
cp_modbus_connection.cpp
Go to the documentation of this file.
1// Copyright 2024 RobosoftAI Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/*****************************************************************************************************************
16 *
17 * Authors: Pablo Inigo Blasco, Brett Aldrich
18 *
19 ******************************************************************************************************************/
20
22
23namespace cl_modbus_tcp_relay
24{
25
27: ISmaccUpdatable(rclcpp::Duration::from_seconds(1.0)), // Default 1 second update period
28 ip_address_("192.168.1.254"),
29 port_(502),
30 slave_id_(1),
31 heartbeat_interval_ms_(1000),
32 connect_on_init_(true),
33 ctx_(nullptr),
34 connected_(false)
35{
36}
37
39{
40 disconnect();
41 if (ctx_)
42 {
43 modbus_free(ctx_);
44 ctx_ = nullptr;
45 }
46}
47
48template <typename T>
50 const std::string & name, T & value, const T & default_val)
51{
52 auto node = getNode();
53 if (!node->has_parameter(name))
54 {
55 node->declare_parameter(name, default_val);
56 }
57 node->get_parameter(name, value);
58}
59
60// Explicit specialization for string type
61template <>
62void CpModbusConnection::declareAndLoadParam<std::string>(
63 const std::string & name, std::string & value, const std::string & default_val)
64{
65 auto node = getNode();
66 if (!node->has_parameter(name))
67 {
68 node->declare_parameter(name, default_val);
69 }
70 node->get_parameter(name, value);
71 RCLCPP_INFO(getLogger(), "[CpModbusConnection] %s: %s", name.c_str(), value.c_str());
72}
73
75{
76 RCLCPP_INFO(getLogger(), "[CpModbusConnection] Initializing...");
77
78 // Load configuration from YAML with defaults
79 declareAndLoadParam("modbus_relay.ip_address", ip_address_, std::string("192.168.1.254"));
80 declareAndLoadParam("modbus_relay.port", port_, 502);
81 declareAndLoadParam("modbus_relay.slave_id", slave_id_, 1);
82 declareAndLoadParam("modbus_relay.heartbeat_interval_ms", heartbeat_interval_ms_, 1000);
83 declareAndLoadParam("modbus_relay.connect_on_init", connect_on_init_, true);
84
85 RCLCPP_INFO(
86 getLogger(),
87 "[CpModbusConnection] Config: %s:%d (slave=%d, heartbeat=%dms, connect_on_init=%s)",
89 connect_on_init_ ? "true" : "false");
90
91 // Set update period for heartbeat
92 this->setUpdatePeriod(rclcpp::Duration::from_seconds(heartbeat_interval_ms_ / 1000.0));
93
94 // Create modbus context
95 ctx_ = modbus_new_tcp(ip_address_.c_str(), port_);
96 if (ctx_)
97 {
98 modbus_set_slave(ctx_, slave_id_);
99 modbus_set_response_timeout(ctx_, 1, 0); // 1 second timeout
100
102 {
103 connect();
104 }
105 }
106 else
107 {
108 std::string error_msg = "Failed to create modbus context";
109 RCLCPP_ERROR(getLogger(), "[CpModbusConnection] %s", error_msg.c_str());
110 onConnectionError_(error_msg);
111 }
112}
113
115{
116 std::lock_guard<std::mutex> lock(mutex_);
117
118 if (!ctx_)
119 {
120 RCLCPP_ERROR(getLogger(), "[CpModbusConnection] Cannot connect: context is null");
121 return false;
122 }
123
124 if (connected_)
125 {
126 RCLCPP_DEBUG(getLogger(), "[CpModbusConnection] Already connected");
127 return true;
128 }
129
130 RCLCPP_INFO(
131 getLogger(), "[CpModbusConnection] Connecting to %s:%d...", ip_address_.c_str(), port_);
132
133 int rc = modbus_connect(ctx_);
134 if (rc == -1)
135 {
136 std::string error_msg = std::string("Connection failed: ") + modbus_strerror(errno);
137 RCLCPP_ERROR(getLogger(), "[CpModbusConnection] %s", error_msg.c_str());
138 onConnectionError_(error_msg);
139 return false;
140 }
141
142 connected_ = true;
143 RCLCPP_INFO(getLogger(), "[CpModbusConnection] Connected successfully");
144 return true;
145}
146
148{
149 std::lock_guard<std::mutex> lock(mutex_);
150
151 if (ctx_ && connected_)
152 {
153 modbus_close(ctx_);
154 connected_ = false;
155 RCLCPP_INFO(getLogger(), "[CpModbusConnection] Disconnected");
156 }
157}
158
160{
161 RCLCPP_INFO(getLogger(), "[CpModbusConnection] Reconnecting...");
162 disconnect();
163 return connect();
164}
165
167{
168 std::lock_guard<std::mutex> lock(mutex_);
169 return connected_;
170}
171
172modbus_t * CpModbusConnection::getContext() { return ctx_; }
173
174std::mutex & CpModbusConnection::getMutex() { return mutex_; }
175
177{
178 std::lock_guard<std::mutex> lock(mutex_);
179
180 if (!ctx_)
181 {
182 return;
183 }
184
185 if (!connected_)
186 {
187 // Try to reconnect if not connected
188 RCLCPP_DEBUG(getLogger(), "[CpModbusConnection] Not connected, attempting to connect...");
189 int rc = modbus_connect(ctx_);
190 if (rc == 0)
191 {
192 connected_ = true;
193 RCLCPP_INFO(getLogger(), "[CpModbusConnection] Connection restored");
196 {
198 }
199 }
200 return;
201 }
202
203 // Heartbeat: Try to read a single coil to verify connectivity
204 uint8_t status;
205 int rc = modbus_read_bits(ctx_, 0x0000, 1, &status);
206
207 if (rc == -1)
208 {
209 connected_ = false;
210 std::string error_msg = modbus_strerror(errno);
211 RCLCPP_WARN(getLogger(), "[CpModbusConnection] Heartbeat failed: %s", error_msg.c_str());
214 {
216 }
217 }
218 else
219 {
220 RCLCPP_DEBUG(getLogger(), "[CpModbusConnection] Heartbeat OK (coil 0 status: %d)", status);
221 }
222}
223
224} // namespace cl_modbus_tcp_relay
smacc2::SmaccSignal< void()> onConnectionLost_
void declareAndLoadParam(const std::string &name, T &value, const T &default_val)
smacc2::SmaccSignal< void()> onConnectionRestored_
smacc2::SmaccSignal< void(const std::string &)> onConnectionError_
rclcpp::Logger getLogger() const
rclcpp::Node::SharedPtr getNode()
void setUpdatePeriod(rclcpp::Duration duration)