SMACC2
Loading...
Searching...
No Matches
cp_modbus_relay.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
26CpModbusRelay::CpModbusRelay() : connectionComponent_(nullptr) {}
27
29
31{
32 RCLCPP_INFO(getLogger(), "[CpModbusRelay] Initializing...");
33
34 // Require the connection component
36
38 {
39 RCLCPP_ERROR(getLogger(), "[CpModbusRelay] Failed to get CpModbusConnection component");
40 }
41 else
42 {
43 RCLCPP_INFO(getLogger(), "[CpModbusRelay] Initialized with connection component");
44 }
45}
46
47bool CpModbusRelay::writeCoil(int channel, bool state)
48{
49 if (!isValidChannel(channel))
50 {
51 std::string error = "Invalid channel: " + std::to_string(channel) + " (must be 1-8)";
52 RCLCPP_ERROR(getLogger(), "[CpModbusRelay] %s", error.c_str());
53 onWriteFailure_(channel, error);
55 return false;
56 }
57
59 {
60 std::string error = "Not connected to Modbus device";
61 RCLCPP_ERROR(getLogger(), "[CpModbusRelay] %s", error.c_str());
62 onWriteFailure_(channel, error);
64 return false;
65 }
66
67 std::lock_guard<std::mutex> lock(connectionComponent_->getMutex());
68 modbus_t * ctx = connectionComponent_->getContext();
69
70 int address = channelToAddress(channel);
71 int value = state ? TRUE : FALSE;
72
73 RCLCPP_INFO(
74 getLogger(), "[CpModbusRelay] Writing coil %d (channel %d) = %s", address, channel,
75 state ? "ON" : "OFF");
76
77 int rc = modbus_write_bit(ctx, address, value);
78
79 if (rc == -1)
80 {
81 std::string error = modbus_strerror(errno);
82 RCLCPP_ERROR(getLogger(), "[CpModbusRelay] Write failed: %s", error.c_str());
83 onWriteFailure_(channel, error);
85 return false;
86 }
87
88 RCLCPP_INFO(getLogger(), "[CpModbusRelay] Write successful");
89 onWriteSuccess_(channel, state);
91 return true;
92}
93
94bool CpModbusRelay::readCoil(int channel, bool & state)
95{
96 if (!isValidChannel(channel))
97 {
98 RCLCPP_ERROR(getLogger(), "[CpModbusRelay] Invalid channel: %d (must be 1-8)", channel);
99 return false;
100 }
101
103 {
104 RCLCPP_ERROR(getLogger(), "[CpModbusRelay] Not connected to Modbus device");
105 return false;
106 }
107
108 std::lock_guard<std::mutex> lock(connectionComponent_->getMutex());
109 modbus_t * ctx = connectionComponent_->getContext();
110
111 int address = channelToAddress(channel);
112 uint8_t status;
113
114 int rc = modbus_read_bits(ctx, address, 1, &status);
115
116 if (rc == -1)
117 {
118 RCLCPP_ERROR(getLogger(), "[CpModbusRelay] Read failed: %s", modbus_strerror(errno));
119 return false;
120 }
121
122 state = (status != 0);
123 RCLCPP_DEBUG(
124 getLogger(), "[CpModbusRelay] Read coil %d (channel %d) = %s", address, channel,
125 state ? "ON" : "OFF");
126 return true;
127}
128
130{
131 // Create a mask with all bits set or cleared
132 uint8_t mask = state ? 0xFF : 0x00;
133 return writeAllCoils(mask);
134}
135
137{
139 {
140 std::string error = "Not connected to Modbus device";
141 RCLCPP_ERROR(getLogger(), "[CpModbusRelay] %s", error.c_str());
142 onWriteFailure_(0, error);
144 return false;
145 }
146
147 std::lock_guard<std::mutex> lock(connectionComponent_->getMutex());
148 modbus_t * ctx = connectionComponent_->getContext();
149
150 // Convert bitmask to array of coil states
151 uint8_t coils[NUM_CHANNELS];
152 for (int i = 0; i < NUM_CHANNELS; i++)
153 {
154 coils[i] = (mask & (1 << i)) ? TRUE : FALSE;
155 }
156
157 RCLCPP_INFO(
158 getLogger(), "[CpModbusRelay] Writing all %d coils with mask 0x%02X", NUM_CHANNELS, mask);
159
160 int rc = modbus_write_bits(ctx, COIL_BASE_ADDRESS, NUM_CHANNELS, coils);
161
162 if (rc == -1)
163 {
164 std::string error = modbus_strerror(errno);
165 RCLCPP_ERROR(getLogger(), "[CpModbusRelay] Write all failed: %s", error.c_str());
166 onWriteFailure_(0, error);
168 return false;
169 }
170
171 RCLCPP_INFO(getLogger(), "[CpModbusRelay] Write all successful");
172
173 // Emit success for each channel that was turned on
174 for (int i = 0; i < NUM_CHANNELS; i++)
175 {
176 bool state = (mask & (1 << i)) != 0;
177 onWriteSuccess_(i + 1, state);
178 }
180 return true;
181}
182
183bool CpModbusRelay::readAllCoils(uint8_t & states)
184{
186 {
187 RCLCPP_ERROR(getLogger(), "[CpModbusRelay] Not connected to Modbus device");
188 return false;
189 }
190
191 std::lock_guard<std::mutex> lock(connectionComponent_->getMutex());
192 modbus_t * ctx = connectionComponent_->getContext();
193
194 uint8_t coils[NUM_CHANNELS];
195
196 int rc = modbus_read_bits(ctx, COIL_BASE_ADDRESS, NUM_CHANNELS, coils);
197
198 if (rc == -1)
199 {
200 RCLCPP_ERROR(getLogger(), "[CpModbusRelay] Read all failed: %s", modbus_strerror(errno));
201 return false;
202 }
203
204 // Convert array to bitmask
205 states = 0;
206 for (int i = 0; i < NUM_CHANNELS; i++)
207 {
208 if (coils[i])
209 {
210 states |= (1 << i);
211 }
212 }
213
214 RCLCPP_DEBUG(getLogger(), "[CpModbusRelay] Read all coils: 0x%02X", states);
215 onReadComplete_(states);
216 return true;
217}
218
219} // namespace cl_modbus_tcp_relay
bool isValidChannel(int channel) const
smacc2::SmaccSignal< void(uint8_t states)> onReadComplete_
std::function< void()> postWriteSuccessEvent_
smacc2::SmaccSignal< void(int channel, bool state)> onWriteSuccess_
int channelToAddress(int channel) const
smacc2::SmaccSignal< void(int channel, const std::string &error)> onWriteFailure_
bool readCoil(int channel, bool &state)
bool writeCoil(int channel, bool state)
std::function< void()> postWriteFailureEvent_
rclcpp::Logger getLogger() const
void requiresComponent(TComponent *&requiredComponentStorage, ComponentRequirement requirementType=ComponentRequirement::SOFT)