My Project
serial_client.cpp
1 /*
2  * uuid-modbus - Microcontroller asynchronous Modbus library
3  * Copyright 2021-2022 Simon Arlott
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <uuid/modbus.h>
20 
21 #include <Arduino.h>
22 
23 #include <algorithm>
24 #include <cstdarg>
25 #include <cstdint>
26 #include <memory>
27 
28 #include <uuid/log.h>
29 
30 namespace uuid {
31 
32 namespace modbus {
33 
34 SerialClient::SerialClient(::HardwareSerial &serial) : serial_(serial) {
35 }
36 
38  if (requests_.empty() || idle_frame_) {
39  idle();
40  return;
41  }
42 
43  auto &response = requests_.front()->response();
44 
45  if (response.status() == ResponseStatus::QUEUED) {
46  idle();
47 
48  if (idle_frame_) {
49  return;
50  }
51 
52  encode();
53  }
54 
55  if (response.status() == ResponseStatus::TRANSMIT) {
56  transmit();
57  }
58 
59  if (response.status() == ResponseStatus::WAITING) {
60  receive();
61  }
62 
63  if (response.done()) {
64  requests_.pop_front();
65  }
66 }
67 
69  uint32_t now_ms = input();
70 
71  if (frame_pos_ == 0) {
72  return;
73  } else {
74  idle_frame_ = true;
75  }
76 
77  if (now_ms - last_rx_ms_ >= INTER_FRAME_TIMEOUT_MS) {
78  log_frame(F("<-"));
79  logger.err(F("Received unexpected frame while idle from device %u"), frame_[0]);
80  frame_pos_ = 0;
81  idle_frame_ = false;
82  }
83 }
84 
86  auto &request = *requests_.front().get();
87  auto &response = request.response();
88 
89  frame_pos_ = request.encode(frame_);
90 
92  response.status(ResponseStatus::FAILURE_INVALID);
93  return;
94  }
95 
96  uint16_t crc = calc_crc();
97  frame_[frame_pos_++] = crc & 0xFF;
98  frame_[frame_pos_++] = crc >> 8;
99 
101  response.status(ResponseStatus::TRANSMIT);
102 
103  log_frame(F("->"));
104  frame_pos_ = 0;
105 }
106 
108  while (frame_pos_ < tx_frame_size_) {
109  int available = serial_.availableForWrite();
110 
111  if (available <= 0) {
112  return;
113  }
114 
115  int len = std::min(available, tx_frame_size_ - frame_pos_);
116 
117  serial_.write(&frame_[frame_pos_], len);
118  frame_pos_ += len;
119 
120  last_tx_ms_ = ::millis();
121  }
122 
123  frame_pos_ = 0;
124  requests_.front()->response().status(ResponseStatus::WAITING);
125 }
126 
128  uint32_t now_ms = ::millis();
129  int data = 0;
130 
131  do {
132  int available = serial_.available();
133 
134  if (available <= 0) {
135  break;
136  }
137 
138  while (available-- > 0) {
139  data = serial_.read();
140 
141  if (data == -1) {
142  break;
143  }
144 
145  if (frame_pos_ < frame_.size()) {
146  frame_[frame_pos_++] = data;
147  }
148 
149  now_ms = ::millis();
150  last_rx_ms_ = now_ms;
151  }
152  } while (data != -1);
153 
154  return now_ms;
155 }
156 
158  uint32_t now_ms = input();
159 
160  if (frame_pos_ == 0) {
161  auto &request = *requests_.front().get();
162 
163  if ((now_ms - last_tx_ms_) >= request.timeout_ms()) {
164  if (request.device() == DeviceAddressType::BROADCAST) {
165  request.response().status(ResponseStatus::SUCCESS);
166  } else {
167  request.response().status(ResponseStatus::FAILURE_TIMEOUT);
168  logger.notice(F("Timeout waiting for response to function %02X from device %u"),
169  frame_[1], frame_[0]);
170  }
171  }
172  } else if (now_ms - last_rx_ms_ >= INTER_FRAME_TIMEOUT_MS) {
173  complete();
174  frame_pos_ = 0;
175  }
176 }
177 
179  auto &request = *requests_.front().get();
180  auto &response = request.response();
181 
182  log_frame(F("<-"));
183 
185  response.status(ResponseStatus::FAILURE_TOO_SHORT);
186  logger.err(F("Received short frame from device %u"), frame_[0]);
187  return;
188  }
189 
191  response.status(ResponseStatus::FAILURE_TOO_LONG);
192  logger.err(F("Received oversized frame from device %u"), frame_[0]);
193  return;
194  }
195 
196  uint16_t act_crc = (frame_[frame_pos_ - 1] << 8) | frame_[frame_pos_ - 2];
198  uint16_t exp_crc = calc_crc();
199 
200  if (exp_crc != act_crc) {
201  response.status(ResponseStatus::FAILURE_CRC);
202  logger.err(F("Received frame with invalid CRC %04X from device %u with function %02X, expected %04X"),
203  act_crc, frame_[0], frame_[1], exp_crc);
204  return;
205  }
206 
207  if (request.device() == DeviceAddressType::BROADCAST) {
208  response.status(ResponseStatus::FAILURE_UNEXPECTED);
209  logger.err(F("Received unexpected broadcast response with function code %02X from device %u"),
210  frame_[1], frame_[0]);
211  return;
212  }
213 
214  if (frame_[0] != request.device()) {
215  response.status(ResponseStatus::FAILURE_ADDRESS);
216  logger.err(F("Received function %02X from device %u, expected device %u"),
217  frame_[1], frame_[0], request.device());
218  return;
219  }
220 
221  if ((frame_[1] & ~0x80) != request.function_code()) {
222  response.status(ResponseStatus::FAILURE_FUNCTION);
223  logger.err(F("Received function %02X from device %u, expected function %02X"),
224  frame_[1], frame_[0], request.function_code());
225  return;
226  }
227 
228  if (frame_[1] & 0x80) {
229  if (frame_pos_ < 3) {
230  response.status(ResponseStatus::FAILURE_LENGTH);
231  logger.err(F("Exception with no code for function %02X from device %u"),
232  frame_[1] & ~0x80, frame_[0]);
233  } else {
234  response.status(ResponseStatus::EXCEPTION);
235  response.exception_code(frame_[2]);
236  logger.notice(F("Exception code %02X for function %02X from device %u"),
237  response.exception_code(), frame_[1] & ~0x80, frame_[0]);
238  }
239  return;
240  }
241 
242  response.status(response.parse(frame_, frame_pos_));
243 }
244 
245 void SerialClient::log_frame(const __FlashStringHelper *prefix) {
246  if (logger.enabled(uuid::log::Level::TRACE)) {
247  static constexpr uint8_t BYTES_PER_LINE = 16;
248  static constexpr uint8_t CHARS_PER_BYTE = 3;
249  std::vector<char> message(CHARS_PER_BYTE * BYTES_PER_LINE + 1);
250  uint8_t pos = 0;
251 
252  for (uint16_t i = 0; i < frame_pos_; i++) {
253  snprintf_P(&message[CHARS_PER_BYTE * pos++], CHARS_PER_BYTE + 1,
254  PSTR("%c%02X"),
256  ? '\'' : ' ',
257  frame_[i]);
258 
259  if (pos == BYTES_PER_LINE || i == frame_pos_ - 1) {
260  logger.trace(F("%S%s"), prefix, message.data());
261  pos = 0;
262  prefix = F(" ");
263  }
264  }
265  }
266 }
267 
268 uint16_t SerialClient::calc_crc() const {
269  uint16_t crc = 0xFFFF;
270 
271  for (uint16_t i = 0; i < frame_pos_; i++) {
272  crc = crc ^ frame_[i];
273 
274  for (uint8_t b = 0; b < 8; b++) {
275  if (crc & 0x0001) {
276  crc >>= 1;
277  crc ^= 0xA001;
278  } else {
279  crc >>= 1;
280  }
281  }
282  }
283 
284  return crc;
285 }
286 
287 } // namespace modbus
288 
289 } // namespace uuid
uuid::log::Logger::err
void err(const char *format,...) const
Log a message at level Level::ERR.
Definition: log.cpp:170
uuid::modbus::MAX_MESSAGE_SIZE
constexpr uint16_t MAX_MESSAGE_SIZE
Definition: modbus.h:47
uuid::modbus::SerialClient::last_rx_ms_
uint32_t last_rx_ms_
Definition: modbus.h:657
uuid::modbus::INTER_FRAME_TIMEOUT_MS
constexpr uint32_t INTER_FRAME_TIMEOUT_MS
Timeout between frames (in milliseconds).
Definition: modbus.h:61
uuid::log::Logger::notice
void notice(const char *format,...) const
Log a message at level Level::NOTICE.
Definition: log.cpp:210
uuid::modbus::SerialClient::receive
void receive()
Receive a message frame and identify the end of a message frame (or timeout).
Definition: serial_client.cpp:157
uuid::modbus::SerialClient::last_tx_ms_
uint32_t last_tx_ms_
Definition: modbus.h:660
uuid::log::Logger::trace
void trace(const char *format,...) const
Log a message at level Level::TRACE.
Definition: log.cpp:270
uuid::modbus::SerialClient::serial_
::HardwareSerial & serial_
Definition: modbus.h:647
uuid::modbus::SerialClient::log_frame
void log_frame(const __FlashStringHelper *prefix)
Log the contents of the current message frame.
Definition: serial_client.cpp:245
uuid::modbus::logger
const uuid::log::Logger logger
Definition: modbus.cpp:37
uuid::modbus::SerialClient::encode
void encode()
Encode the request message at the top of the queue.
Definition: serial_client.cpp:85
uuid::modbus::SerialClient::idle
void idle()
Receive messages while idle.
Definition: serial_client.cpp:68
uuid::modbus::MESSAGE_HEADER_SIZE
constexpr uint16_t MESSAGE_HEADER_SIZE
Definition: modbus.h:48
uuid::modbus::SerialClient::frame_pos_
uint16_t frame_pos_
Definition: modbus.h:655
uuid::modbus::SerialClient::SerialClient
SerialClient(::HardwareSerial &serial)
Create a new client.
Definition: serial_client.cpp:34
uuid::modbus::SerialClient::calc_crc
uint16_t calc_crc() const
Calculate CRC for the current frame;.
Definition: serial_client.cpp:268
uuid::log::Logger::enabled
bool enabled(Level level) const
Determine if the specified log level is enabled by the effective log level.
Definition: log.h:422
uuid::modbus::SerialClient::loop
void loop()
Loop function that must be called regularly to send and receive messages.
Definition: serial_client.cpp:37
uuid
Common utilities.
Definition: get_uptime_ms.cpp:28
uuid::modbus::SerialClient::tx_frame_size_
uint16_t tx_frame_size_
Definition: modbus.h:659
uuid::modbus::SerialClient::input
uint32_t input()
Read message frames from the serial port device.
Definition: serial_client.cpp:127
uuid::modbus::SerialClient::transmit
void transmit()
Transmit the current message frame on the serial port device.
Definition: serial_client.cpp:107
uuid::modbus::SerialClient::idle_frame_
bool idle_frame_
Definition: modbus.h:652
uuid::modbus::MESSAGE_CRC_SIZE
constexpr uint16_t MESSAGE_CRC_SIZE
Definition: modbus.h:49
uuid::modbus::SerialClient::complete
void complete()
Finish current request and populate response.
Definition: serial_client.cpp:178
uuid::modbus::SerialClient::requests_
std::deque< std::unique_ptr< Request > > requests_
Definition: modbus.h:648
uuid::modbus::SerialClient::frame_
frame_buffer_t frame_
Definition: modbus.h:654