OSSIA
Open Scenario System for Interactive Application
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Modules Pages
serial_socket.hpp
1#pragma once
2#include <ossia/network/sockets/configuration.hpp>
3#include <ossia/network/sockets/writers.hpp>
4
5#include <boost/asio/io_context.hpp>
6#include <boost/asio/placeholders.hpp>
7#include <boost/asio/post.hpp>
8#include <boost/asio/serial_port.hpp>
9#include <boost/asio/write.hpp>
10
11#include <nano_signal_slot.hpp>
12#if defined(__linux__)
13#include <linux/serial.h>
14#include <linux/tty_flags.h>
15#include <sys/ioctl.h>
16
17#include <termios.h>
18#elif defined(__APPLE__)
19#include <sys/ioctl.h>
20#endif
21
22namespace ossia::net
23{
24
25template <typename Framing>
26class serial_socket
27{
28public:
29 using proto = boost::asio::serial_port;
30 using socket = boost::asio::serial_port;
31 using encoder = typename Framing::template encoder<boost::asio::serial_port>;
32 using decoder = typename Framing::template decoder<boost::asio::serial_port>;
33
34 serial_socket(const serial_configuration& conf, boost::asio::io_context& ctx)
35 : m_context{ctx}
36 , m_conf{std::move(conf)}
37 , m_port{ctx}
38 , m_encoder{this->m_port}
39 , m_decoder{this->m_port}
40 {
41 }
42
43 int set_custom_baud_rate(int rate)
44 {
45#if defined(__linux__)
46 // https://stackoverflow.com/questions/9366249/boostasioserialport-alternative-that-supports-non-standard-baud-rates
47 auto fd = m_port.native_handle();
48 struct serial_struct serinfo;
49 memset(&serinfo, 0, sizeof(serinfo));
50
51 // Get the current serial status
52 if(ioctl(fd, TIOCGSERIAL, &serinfo) < 0)
53 return -1;
54
55 // Device can handle any rate e.g. it's using straight USB packetization
56 if(serinfo.baud_base == 0)
57 return 0;
58
59 // Approximate the closest baud rate
60 serinfo.flags &= ~ASYNC_SPD_MASK;
61 serinfo.flags |= ASYNC_SPD_CUST;
62 serinfo.custom_divisor = (serinfo.baud_base + (rate / 2)) / rate;
63 if(serinfo.custom_divisor < 1)
64 {
65 serinfo.baud_base = rate;
66 serinfo.custom_divisor = 1;
67 }
68
69 // Apply and check
70 if(ioctl(fd, TIOCSSERIAL, &serinfo) < 0)
71 return -1;
72 if(ioctl(fd, TIOCGSERIAL, &serinfo) < 0)
73 return -1;
74
75 fcntl(fd, F_SETFL, 0);
76
77 // Apply terminal options
78 struct termios options;
79 tcgetattr(fd, &options);
80 cfsetispeed(&options, B38400);
81 cfsetospeed(&options, B38400);
82 cfmakeraw(&options);
83 options.c_cflag |= (CLOCAL | CREAD);
84 options.c_cflag &= ~CRTSCTS;
85 if(tcsetattr(fd, TCSANOW, &options) != 0)
86 return -1;
87
88#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) \
89 || defined(__OpenBSD__) || defined(__DragonFly__)
90 auto fd = m_port.native_handle();
91#define IOSSIOSPEED _IOW('T', 2, speed_t)
92 if(ioctl(fd, IOSSIOSPEED, &rate, 1) < 0)
93 return -1;
94#undef IOSSIOSPEED
95#endif
96
97 return 0;
98 }
99
100 void connect()
101 {
102 m_port.open(m_conf.port);
103
104 try
105 {
106 m_port.set_option(proto::baud_rate(m_conf.baud_rate));
107 }
108 catch(...)
109 {
110 m_port.set_option(proto::baud_rate(38400));
111
112 int new_baud = static_cast<int>(m_conf.baud_rate);
113 set_custom_baud_rate(new_baud);
114 }
115 m_port.set_option(proto::character_size(m_conf.character_size));
116 m_port.set_option(proto::flow_control(
117 static_cast<proto::flow_control::type>(m_conf.flow_control)));
118 m_port.set_option(proto::parity(static_cast<proto::parity::type>(m_conf.parity)));
119 m_port.set_option(
120 proto::stop_bits(static_cast<proto::stop_bits::type>(m_conf.stop_bits)));
121
122 boost::asio::post(m_context, [this] { on_open(); });
123 }
124
125 template <typename F>
126 void receive(F f)
127 {
128 struct proc : stream_processor<serial_socket, F>
129 {
130 };
131 m_decoder.receive(proc{*this, std::move(f)});
132 }
133
134 void close()
135 {
136 if(m_port.is_open())
137 {
138 m_port.cancel();
139 boost::asio::post(m_context, [this] {
140 m_port.close();
141 on_close();
142 });
143 }
144 }
145
146 void write(const char* data, std::size_t sz) { m_encoder.write(data, sz); }
147
148 bool connected() const noexcept { return true; }
149
150 Nano::Signal<void()> on_open;
151 Nano::Signal<void()> on_close;
152 Nano::Signal<void()> on_fail;
153
154 boost::asio::io_context& m_context;
155 serial_configuration m_conf;
156 boost::asio::serial_port m_port;
157 encoder m_encoder;
158 decoder m_decoder;
159};
160}