|
|
@@ -0,0 +1,290 @@
|
|
|
+#include "ndi_interface.h"
|
|
|
+
|
|
|
+#include "utility/debug_utility.hpp"
|
|
|
+#include "utility/coro_signal.hpp"
|
|
|
+#include "utility/name_translator.hpp"
|
|
|
+#include "utility/tiny_signal.hpp"
|
|
|
+#include "utility/versatile_buffer2.hpp"
|
|
|
+#include "third_party/static_block.hpp"
|
|
|
+
|
|
|
+#include <boost/asio/experimental/awaitable_operators.hpp>
|
|
|
+#include <boost/asio/experimental/channel.hpp>
|
|
|
+#include <boost/asio/ip/address.hpp>
|
|
|
+#include <boost/asio/ip/tcp.hpp>
|
|
|
+#include <boost/asio/co_spawn.hpp>
|
|
|
+#include <boost/asio/detached.hpp>
|
|
|
+#include <boost/asio/serial_port.hpp>
|
|
|
+#include <boost/asio/use_awaitable.hpp>
|
|
|
+#include <boost/crc.hpp>
|
|
|
+#include <boost/iostreams/device/mapped_file.hpp>
|
|
|
+#include <boost/smart_ptr/scoped_ptr.hpp>
|
|
|
+#include <boost/system/error_code.hpp>
|
|
|
+
|
|
|
+#include <spdlog/spdlog.h>
|
|
|
+
|
|
|
+#include <cassert>
|
|
|
+
|
|
|
+namespace sophiar {
|
|
|
+
|
|
|
+ using boost::asio::async_read;
|
|
|
+ using boost::asio::async_write;
|
|
|
+ using boost::asio::awaitable;
|
|
|
+ using boost::asio::buffer;
|
|
|
+ using boost::asio::co_spawn;
|
|
|
+ using boost::asio::detached;
|
|
|
+ using boost::asio::experimental::channel;
|
|
|
+ using boost::asio::serial_port;
|
|
|
+ using boost::asio::use_awaitable;
|
|
|
+ using boost::iostreams::mapped_file;
|
|
|
+ using boost::scoped_ptr;
|
|
|
+ using boost::system::error_code;
|
|
|
+
|
|
|
+ using namespace boost::asio::experimental::awaitable_operators;
|
|
|
+ using namespace boost::asio::ip;
|
|
|
+
|
|
|
+ enum class ndi_address_type {
|
|
|
+ ethernet,
|
|
|
+ serial,
|
|
|
+ };
|
|
|
+
|
|
|
+ name_translator<ndi_address_type> ndi_address_type_translator;
|
|
|
+
|
|
|
+ static_block {
|
|
|
+ ndi_address_type_translator.register_item("ethernet", ndi_address_type::ethernet);
|
|
|
+ ndi_address_type_translator.register_item("serial", ndi_address_type::serial);
|
|
|
+ };
|
|
|
+
|
|
|
+ struct ndi_interface::impl {
|
|
|
+
|
|
|
+ using ascii_reply_content = static_memory<1024>;
|
|
|
+ struct ascii_reply_obj : public ascii_reply_content,
|
|
|
+ public small_obj<ascii_reply_obj> {
|
|
|
+ };
|
|
|
+
|
|
|
+ using binary_reply_content = dynamic_memory;
|
|
|
+
|
|
|
+ struct binary_reply_obj : public binary_reply_content,
|
|
|
+ public small_obj<binary_reply_obj> {
|
|
|
+ FORWARD_CONSTRUCT(binary_reply_obj, binary_reply_content)
|
|
|
+ };
|
|
|
+
|
|
|
+ static constexpr auto reply_queue_size = 4;
|
|
|
+ static constexpr auto ndi_endian = boost::endian::order::little;
|
|
|
+
|
|
|
+ using crc_checker_type = boost::crc_16_type;
|
|
|
+
|
|
|
+ using ascci_reply_queue_type = channel<void(error_code, ascii_reply_obj::pointer)>;
|
|
|
+ ascci_reply_queue_type ascii_reply_queue;
|
|
|
+
|
|
|
+ using binary_reply_queue_type = channel<void(error_code, binary_reply_obj::pointer)>;
|
|
|
+ binary_reply_queue_type binary_reply_queue;
|
|
|
+ binary_reply_queue_type stream_reply_queue; // TODO 如果同时需要多个 stream, 则考虑建一个哈希池
|
|
|
+
|
|
|
+ coro_signal stop_requested_signal;
|
|
|
+
|
|
|
+ ndi_address_type address_type;
|
|
|
+
|
|
|
+ // If address type is ethernet
|
|
|
+ using ip_address_type = boost::asio::ip::address;
|
|
|
+ ip_address_type ndi_ip;
|
|
|
+ uint16_t ndi_port;
|
|
|
+ scoped_ptr<tcp::socket> ndi_tcp_socket;
|
|
|
+
|
|
|
+ // If address type is serial
|
|
|
+ std::string com_port_name;
|
|
|
+ scoped_ptr<serial_port> ndi_com_socket;
|
|
|
+
|
|
|
+ void load_init_config(const nlohmann::json &config) {
|
|
|
+ assert(config.contains("address_type"));
|
|
|
+ assert(config["address_type"].is_string());
|
|
|
+ address_type = ndi_address_type_translator.translate(config["address_type"].get<std::string>());
|
|
|
+ if (address_type == ndi_address_type::ethernet) {
|
|
|
+ assert(config.contains("ip"));
|
|
|
+ assert(config["ip"].is_string());
|
|
|
+ ndi_ip = make_address(config["ip"].get<std::string>());
|
|
|
+ assert(config.contains("tcp_port"));
|
|
|
+ assert(config["tcp_port"].is_number_unsigned());
|
|
|
+ ndi_port = config["tcp_port"].get<uint64_t>();
|
|
|
+ } else if (address_type == ndi_address_type::serial) {
|
|
|
+ assert(config.contains("com_port"));
|
|
|
+ assert(config["com_port"].is_string());
|
|
|
+ com_port_name = config["com_port"].get<std::string>();
|
|
|
+ }
|
|
|
+ assert(false);
|
|
|
+ }
|
|
|
+
|
|
|
+ awaitable<binary_reply_obj::pointer> read_binary_reply(uint16_t header_magic) {
|
|
|
+ // read length and crc
|
|
|
+ using header_reader_type = versatile_readable_buffer<ndi_endian, static_memory<4>>;
|
|
|
+ header_reader_type header_reader;
|
|
|
+ if (address_type == ndi_address_type::serial) {
|
|
|
+ co_await header_reader.async_read_from(*ndi_com_socket, 4);
|
|
|
+ } else if (address_type == ndi_address_type::ethernet) {
|
|
|
+ co_await header_reader.async_read_from(*ndi_tcp_socket, 4);
|
|
|
+ }
|
|
|
+ uint16_t reply_length, header_crc16;
|
|
|
+ header_reader >> reply_length >> header_crc16;
|
|
|
+
|
|
|
+ // check header crc
|
|
|
+ crc_checker_type crc_checker;
|
|
|
+ uint16_t header_magic_ndi = header_magic;
|
|
|
+ swap_net_loc_endian<ndi_endian>(header_magic_ndi);
|
|
|
+ crc_checker.process_bytes(&header_magic_ndi, 2); // header magic
|
|
|
+ crc_checker.process_bytes(header_reader.get_buffer().data(), 2); // reply size
|
|
|
+ if (crc_checker.checksum() != header_crc16) {
|
|
|
+ // TODO show log
|
|
|
+ co_return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ // read reply content
|
|
|
+ auto binary_reply = binary_reply_obj::new_instance(reply_length + 2); // 2 for crc
|
|
|
+ if (address_type == ndi_address_type::serial) {
|
|
|
+ co_await async_read(*ndi_com_socket,
|
|
|
+ buffer(binary_reply->data(), reply_length + 2),
|
|
|
+ use_awaitable);
|
|
|
+ } else if (address_type == ndi_address_type::ethernet) {
|
|
|
+ co_await async_read(*ndi_tcp_socket,
|
|
|
+ buffer(binary_reply->data(), reply_length + 2),
|
|
|
+ use_awaitable);
|
|
|
+ }
|
|
|
+
|
|
|
+ // check reply crc
|
|
|
+ crc_checker.reset();
|
|
|
+ crc_checker.process_bytes(binary_reply->data(), reply_length);
|
|
|
+ uint16_t reply_crc;
|
|
|
+ memcpy(&reply_crc, binary_reply->data() + reply_length, 2);
|
|
|
+ swap_net_loc_endian<ndi_endian>(reply_crc);
|
|
|
+ if (crc_checker.checksum() != reply_crc) {
|
|
|
+ // TODO show log
|
|
|
+ co_return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+ co_return binary_reply;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 读取并分发 "一份" 来自 NDI 的回复消息
|
|
|
+ awaitable<void> read_and_dispatch_reply() {
|
|
|
+ static constexpr uint16_t binary_reply_header_magic = 0xA5C4;
|
|
|
+ static constexpr uint16_t extended_binary_reply_header_magic = 0xA5C8;
|
|
|
+ static constexpr uint16_t streaming_header_magic = 0xB5D4;
|
|
|
+
|
|
|
+ // 读取前两个字节, 判断报文类型
|
|
|
+ uint16_t header_magic;
|
|
|
+ if (address_type == ndi_address_type::serial) {
|
|
|
+ co_await async_read_value<ndi_endian>(*ndi_com_socket, header_magic);
|
|
|
+ } else if (address_type == ndi_address_type::ethernet) {
|
|
|
+ co_await async_read_value<ndi_endian>(*ndi_tcp_socket, header_magic);
|
|
|
+ } else {
|
|
|
+ assert(false);
|
|
|
+ co_return;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (header_magic) {
|
|
|
+ case binary_reply_header_magic: {
|
|
|
+ auto binary_reply = co_await read_binary_reply(header_magic);
|
|
|
+ if (binary_reply) {
|
|
|
+ auto ok = binary_reply_queue.try_send(error_code{}, std::move(binary_reply));
|
|
|
+ if (!ok) {
|
|
|
+ // TODO show log
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case extended_binary_reply_header_magic: {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ case streaming_header_magic: {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ default: { // ascii reply
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ co_return;
|
|
|
+ }
|
|
|
+
|
|
|
+ awaitable<void> send_command(std::string &&cmd) {
|
|
|
+ cmd.push_back('\r'); // Add CR to end the command
|
|
|
+ if (address_type == ndi_address_type::serial) {
|
|
|
+ assert(ndi_com_socket->is_open());
|
|
|
+ co_await async_write(*ndi_com_socket, buffer(cmd), use_awaitable);
|
|
|
+ } else if (address_type == ndi_address_type::ethernet) {
|
|
|
+ assert(ndi_tcp_socket->is_open());
|
|
|
+ co_await async_write(*ndi_tcp_socket, buffer(cmd), use_awaitable);
|
|
|
+ } else {
|
|
|
+ assert(false);
|
|
|
+ }
|
|
|
+ co_return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 对串口进行配置
|
|
|
+ awaitable<void> setup_com_socket() {
|
|
|
+ assert(address_type == ndi_address_type::serial);
|
|
|
+
|
|
|
+ ndi_com_socket->open(com_port_name);
|
|
|
+ assert(ndi_com_socket->is_open());
|
|
|
+
|
|
|
+ // load default config
|
|
|
+ ndi_com_socket->set_option(serial_port::baud_rate(9600));
|
|
|
+ ndi_com_socket->set_option(serial_port::character_size(8));
|
|
|
+ ndi_com_socket->set_option(serial_port::parity(serial_port::parity::none));
|
|
|
+ ndi_com_socket->set_option(serial_port::stop_bits(serial_port::stop_bits::one));
|
|
|
+ ndi_com_socket->set_option(serial_port::flow_control(serial_port::flow_control::none));
|
|
|
+
|
|
|
+ ndi_com_socket->send_break(); // cause the system to reset
|
|
|
+
|
|
|
+
|
|
|
+ co_return;
|
|
|
+ }
|
|
|
+
|
|
|
+ awaitable<bool> on_init_impl() {
|
|
|
+ // establish connection
|
|
|
+ if (address_type == ndi_address_type::ethernet) {
|
|
|
+ ndi_tcp_socket.reset(new tcp::socket(get_context()));
|
|
|
+ co_await ndi_tcp_socket->async_connect({ndi_ip, ndi_port}, use_awaitable);
|
|
|
+ // decrease delay
|
|
|
+ tcp::no_delay no_delay_option(true);
|
|
|
+ ndi_tcp_socket->set_option(no_delay_option);
|
|
|
+ } else if (address_type == ndi_address_type::serial) {
|
|
|
+ ndi_com_socket.reset(new serial_port(get_context()));
|
|
|
+ co_await setup_com_socket();
|
|
|
+ } else {
|
|
|
+ co_return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // setup steps
|
|
|
+// versatile_buffer buffer;
|
|
|
+// CO_ENSURE(negotiate_protocol(buffer))
|
|
|
+// co_await print_ur_control_version(buffer);
|
|
|
+// CO_ENSURE(setup_inputs(buffer))
|
|
|
+// CO_ENSURE(setup_outputs(buffer))
|
|
|
+// co_await install_program();
|
|
|
+// co_return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ awaitable<void> on_stop_impl() {
|
|
|
+ // TODO
|
|
|
+ co_return;
|
|
|
+ }
|
|
|
+
|
|
|
+ impl()
|
|
|
+ : ascii_reply_queue(get_context(), reply_queue_size),
|
|
|
+ binary_reply_queue(get_context(), reply_queue_size),
|
|
|
+ stream_reply_queue(get_context(), reply_queue_size),
|
|
|
+ stop_requested_signal(get_context()) {}
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ ndi_interface::ndi_interface()
|
|
|
+ : pimpl(std::make_unique<impl>()) {}
|
|
|
+
|
|
|
+ boost::asio::awaitable<void> ndi_interface::on_stop() {
|
|
|
+ co_await pimpl->on_stop_impl();
|
|
|
+ co_await tristate_obj::on_stop();
|
|
|
+ co_return;
|
|
|
+ }
|
|
|
+
|
|
|
+ ndi_interface::~ndi_interface() = default;
|
|
|
+
|
|
|
+}
|