unilink  0.4.3
A simple C++ library for unified async communication
serial.cc
Go to the documentation of this file.
1 /*
2  * Copyright 2025 Jinwoo Sung
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
18 
19 #include <algorithm>
20 #include <boost/asio/executor_work_guard.hpp>
21 #include <boost/asio/io_context.hpp>
22 #include <cctype>
23 #include <iostream>
24 #include <stdexcept>
25 #include <thread>
26 
27 #include "unilink/base/common.hpp"
30 
31 namespace unilink {
32 namespace wrapper {
33 
34 namespace {
35 std::string to_lower(std::string s) {
36  std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); });
37  return s;
38 }
39 } // namespace
40 
41 struct Serial::Impl {
42  std::string device;
43  uint32_t baud_rate;
44  std::shared_ptr<interface::Channel> channel;
45  std::shared_ptr<boost::asio::io_context> external_ioc;
46  bool use_external_context{false};
48  std::thread external_thread;
49 
50  // Start notification
51  std::promise<bool> start_promise_;
53 
54  // Event handlers (Context based)
59 
60  // Configuration
61  bool auto_manage = false;
62  bool started = false;
63  int data_bits = 8;
64  int stop_bits = 1;
65  std::string parity = "none";
66  std::string flow_control = "none";
67  std::chrono::milliseconds retry_interval{3000};
68 
69  Impl(const std::string& dev, uint32_t baud) : device(dev), baud_rate(baud) {}
70  Impl(const std::string& dev, uint32_t baud, std::shared_ptr<boost::asio::io_context> ioc)
71  : device(dev), baud_rate(baud), external_ioc(std::move(ioc)), use_external_context(external_ioc != nullptr) {}
72  explicit Impl(std::shared_ptr<interface::Channel> ch) : channel(std::move(ch)) {}
73 
74  ~Impl() {
75  try {
76  stop();
77  } catch (...) {
78  }
79  }
80 
81  std::future<bool> start() {
82  if (started) {
83  std::promise<bool> p;
84  p.set_value(true);
85  return p.get_future();
86  }
87 
88  start_promise_ = std::promise<bool>();
90 
91  if (!channel) {
94  }
95 
96  channel->start();
98  external_thread = std::thread([ioc = external_ioc]() {
99  boost::asio::executor_work_guard<boost::asio::io_context::executor_type> guard(ioc->get_executor());
100  ioc->run();
101  });
102  }
103 
104  started = true;
105  return start_promise_.get_future();
106  }
107 
108  void stop() {
109  if (!started) return;
110 
111  if (channel) {
112  channel->on_bytes(nullptr);
113  channel->on_state(nullptr);
114  channel->stop();
115  }
116 
118  if (external_ioc) external_ioc->stop();
119  external_thread.join();
120  }
121 
122  started = false;
123 
125  try {
126  start_promise_.set_value(false);
127  } catch (...) {
128  }
130  }
131  }
132 
134  if (!channel) return;
135 
136  channel->on_bytes([this](memory::ConstByteSpan data) {
137  if (data_handler) {
138  std::string str_data = common::safe_convert::uint8_to_string(data.data(), data.size());
139  data_handler(MessageContext(0, str_data));
140  }
141  });
142 
143  channel->on_state([this](base::LinkState state) {
144  switch (state) {
147  start_promise_.set_value(true);
149  }
151  break;
154  break;
157  start_promise_.set_value(false);
159  }
160  if (error_handler) error_handler(ErrorContext(ErrorCode::IoError, "Connection error"));
161  break;
162  default:
163  break;
164  }
165  });
166  }
167 
169  config::SerialConfig config;
170  config.device = device;
171  config.baud_rate = baud_rate;
172  config.char_size = static_cast<unsigned int>(data_bits);
173  config.stop_bits = static_cast<unsigned int>(stop_bits);
174  std::string p = to_lower(parity);
175  if (p == "even")
177  else if (p == "odd")
179  else
181 
182  std::string f = to_lower(flow_control);
183  if (f == "software")
185  else if (f == "hardware")
187  else
189 
190  config.retry_interval_ms = static_cast<unsigned int>(retry_interval.count());
191  return config;
192  }
193 };
194 
195 Serial::Serial(const std::string& d, uint32_t b) : impl_(std::make_unique<Impl>(d, b)) {}
196 Serial::Serial(const std::string& d, uint32_t b, std::shared_ptr<boost::asio::io_context> i)
197  : impl_(std::make_unique<Impl>(d, b, i)) {}
198 Serial::Serial(std::shared_ptr<interface::Channel> ch) : impl_(std::make_unique<Impl>(ch)) {
199  impl_->setup_internal_handlers();
200 }
201 Serial::~Serial() = default;
202 
203 Serial::Serial(Serial&&) noexcept = default;
204 Serial& Serial::operator=(Serial&&) noexcept = default;
205 
206 std::future<bool> Serial::start() { return impl_->start(); }
207 void Serial::stop() { impl_->stop(); }
208 void Serial::send(std::string_view data) {
209  if (is_connected() && get_impl()->channel) {
210  auto binary_view = common::safe_convert::string_to_bytes(data);
211  get_impl()->channel->async_write_copy(memory::ConstByteSpan(binary_view.first, binary_view.second));
212  }
213 }
214 void Serial::send_line(std::string_view line) { send(std::string(line) + "\n"); }
215 bool Serial::is_connected() const { return get_impl()->channel && get_impl()->channel->is_connected(); }
216 
218  impl_->data_handler = std::move(h);
219  return *this;
220 }
222  impl_->connect_handler = std::move(h);
223  return *this;
224 }
226  impl_->disconnect_handler = std::move(h);
227  return *this;
228 }
230  impl_->error_handler = std::move(h);
231  return *this;
232 }
233 
235  impl_->auto_manage = m;
236  if (impl_->auto_manage && !impl_->started) start();
237  return *this;
238 }
239 
240 void Serial::set_baud_rate(uint32_t b) { impl_->baud_rate = b; }
241 void Serial::set_data_bits(int d) { impl_->data_bits = d; }
242 void Serial::set_stop_bits(int s) { impl_->stop_bits = s; }
243 void Serial::set_parity(const std::string& p) { impl_->parity = p; }
244 void Serial::set_flow_control(const std::string& f) { impl_->flow_control = f; }
245 void Serial::set_retry_interval(std::chrono::milliseconds i) {
246  impl_->retry_interval = i;
247  if (impl_->channel) {
248  auto ts = std::dynamic_pointer_cast<transport::Serial>(impl_->channel);
249  if (ts) ts->set_retry_interval(static_cast<unsigned int>(i.count()));
250  }
251 }
252 
253 config::SerialConfig Serial::build_config() const { return get_impl()->build_config(); }
254 
255 void Serial::set_manage_external_context(bool m) { impl_->manage_external_context = m; }
256 
257 } // namespace wrapper
258 } // namespace unilink