|
@@ -0,0 +1,237 @@
|
|
|
|
|
+#include "optoforce_daq.h"
|
|
|
|
|
+
|
|
|
|
|
+#include "core/basic_obj_types.hpp"
|
|
|
|
|
+#include "utility/coro_worker.hpp"
|
|
|
|
|
+#include "utility/versatile_buffer2.hpp"
|
|
|
|
|
+
|
|
|
|
|
+#include <boost/asio/serial_port.hpp>
|
|
|
|
|
+#include <boost/asio/use_awaitable.hpp>
|
|
|
|
|
+#include <boost/crc.hpp>
|
|
|
|
|
+#include <boost/smart_ptr/scoped_ptr.hpp>
|
|
|
|
|
+
|
|
|
|
|
+#include <spdlog/spdlog.h>
|
|
|
|
|
+
|
|
|
|
|
+#include <numeric>
|
|
|
|
|
+
|
|
|
|
|
+namespace sophiar {
|
|
|
|
|
+
|
|
|
|
|
+ using boost::asio::async_read;
|
|
|
|
|
+ using boost::asio::async_write;
|
|
|
|
|
+ using boost::asio::awaitable;
|
|
|
|
|
+ using boost::asio::serial_port;
|
|
|
|
|
+ using boost::asio::use_awaitable;
|
|
|
|
|
+ using boost::scoped_ptr;
|
|
|
|
|
+
|
|
|
|
|
+ struct optoforce_daq::impl {
|
|
|
|
|
+
|
|
|
|
|
+ static constexpr auto daq_endian = boost::endian::order::big;
|
|
|
|
|
+ static constexpr uint8_t header_magic_1[] = {170, 7, 8, 10};
|
|
|
|
|
+ static constexpr uint8_t header_magic_2[] = {170, 7, 8, 28};
|
|
|
|
|
+ static constexpr uint8_t header_magic_3[] = {170, 7, 8, 16};
|
|
|
|
|
+ static constexpr uint8_t header_magic_config[] = {170, 0, 50, 3};
|
|
|
|
|
+ static constexpr auto message_length_1 = 16;
|
|
|
|
|
+ static constexpr auto message_length_2 = 34;
|
|
|
|
|
+ static constexpr auto message_length_3 = 22;
|
|
|
|
|
+ static constexpr auto message_length_config = 9;
|
|
|
|
|
+
|
|
|
|
|
+ optoforce_daq *q_this = nullptr;
|
|
|
|
|
+
|
|
|
|
|
+ scoped_ptr<serial_port> conn;
|
|
|
|
|
+ uint8_t device_type = 3;
|
|
|
|
|
+ coro_worker::pointer worker;
|
|
|
|
|
+
|
|
|
|
|
+ double force_resolution, torque_resolution;
|
|
|
|
|
+
|
|
|
|
|
+ global_obj_index_type force_obj_index;
|
|
|
|
|
+ global_obj_index_type torque_obj_index;
|
|
|
|
|
+
|
|
|
|
|
+ template<size_t MessageLength>
|
|
|
|
|
+ uint16_t calc_checksum(const char *data) {
|
|
|
|
|
+ return std::accumulate(data, data + MessageLength - 2, static_cast<uint16_t>(0));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ template<size_t MessageLength>
|
|
|
|
|
+ uint16_t get_checksum_val(const char *data) {
|
|
|
|
|
+ uint16_t checksum_val = *reinterpret_cast<const uint16_t *>(data + MessageLength - 2);
|
|
|
|
|
+ swap_net_loc_endian<daq_endian>(checksum_val);
|
|
|
|
|
+ return checksum_val;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ template<size_t MessageLength>
|
|
|
|
|
+ bool check_checksum(const char *data) {
|
|
|
|
|
+ auto checksum_val = calc_checksum<MessageLength>(data);
|
|
|
|
|
+ auto checksum = get_checksum_val<MessageLength>(data);
|
|
|
|
|
+ return checksum_val == checksum;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ awaitable<bool> handle_type_3_message() {
|
|
|
|
|
+ using memory_type = static_memory<message_length_3>;
|
|
|
|
|
+ auto reader = versatile_readable_buffer<daq_endian, memory_type>();
|
|
|
|
|
+ co_await reader.async_read_from(*conn, message_length_3);
|
|
|
|
|
+
|
|
|
|
|
+ // check header and checksum
|
|
|
|
|
+ assert(memcmp(header_magic_3, reader.cur_data(), 4) == 0);
|
|
|
|
|
+ assert(check_checksum<message_length_3>(reader.cur_data()));
|
|
|
|
|
+
|
|
|
|
|
+ reader.manual_offset(4); // ignore header
|
|
|
|
|
+ uint16_t sample_count, status;
|
|
|
|
|
+ reader >> sample_count >> status;
|
|
|
|
|
+ if (status != 0) {
|
|
|
|
|
+ // TODO show log for abnormal status
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // force component
|
|
|
|
|
+ auto ts = current_timestamp();
|
|
|
|
|
+ uint16_t fx, fy, fz;
|
|
|
|
|
+ reader >> fx >> fy >> fz;
|
|
|
|
|
+ auto force_value = Eigen::Vector3d(
|
|
|
|
|
+ fx * force_resolution,
|
|
|
|
|
+ fy * force_resolution,
|
|
|
|
|
+ fz * force_resolution);
|
|
|
|
|
+ UPDATE_GLOBAL_OBJ_VALUE_TS(scalarxyz_obj, force_obj_index, std::move(force_value), ts);
|
|
|
|
|
+
|
|
|
|
|
+ // torque component
|
|
|
|
|
+ uint16_t tx, ty, tz;
|
|
|
|
|
+ reader >> tx >> ty >> tz;
|
|
|
|
|
+ auto torque_value = Eigen::Vector3d(
|
|
|
|
|
+ tx * torque_resolution,
|
|
|
|
|
+ ty * torque_resolution,
|
|
|
|
|
+ tz * torque_resolution);
|
|
|
|
|
+ UPDATE_GLOBAL_OBJ_VALUE_TS(scalarxyz_obj, torque_obj_index, std::move(torque_value), ts);
|
|
|
|
|
+
|
|
|
|
|
+ co_return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ awaitable<void> send_config_message(uint8_t speed, uint8_t filter, uint8_t zero = 0) {
|
|
|
|
|
+ assert(zero == 0); // zero will be done manually
|
|
|
|
|
+ using memory_type = static_memory<message_length_config>;
|
|
|
|
|
+ auto writer = versatile_writable_buffer<daq_endian, memory_type>();
|
|
|
|
|
+ memcpy(writer.cur_data(), header_magic_config, 4);
|
|
|
|
|
+ writer.manual_offset(4);
|
|
|
|
|
+ writer << speed << filter << zero;
|
|
|
|
|
+ auto checksum = calc_checksum<message_length_config>(writer.get_buffer().data());
|
|
|
|
|
+ writer << checksum;
|
|
|
|
|
+ co_await writer.async_write_to(*conn);
|
|
|
|
|
+ co_return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ awaitable<void> load_device_config(const nlohmann::json &config) {
|
|
|
|
|
+ // device type
|
|
|
|
|
+ assert(config.contains("device_type"));
|
|
|
|
|
+ assert(config["device_type"].is_number_unsigned());
|
|
|
|
|
+ device_type = config["device_type"].get<uint8_t>();
|
|
|
|
|
+ assert(device_type == 3); // TODO only type 3 is supported
|
|
|
|
|
+
|
|
|
|
|
+ // global objects
|
|
|
|
|
+ switch (device_type) {
|
|
|
|
|
+ case 3: {
|
|
|
|
|
+ assert(config.contains("force_output_name"));
|
|
|
|
|
+ assert(config.contains("torque_output_name"));
|
|
|
|
|
+ assert(config["force_output_name"].is_string());
|
|
|
|
|
+ assert(config["torque_output_name"].is_string());
|
|
|
|
|
+ auto force_output_name = config["force_output_name"].get<std::string>();
|
|
|
|
|
+ auto torque_output_name = config["torque_output_name"].get<std::string>();
|
|
|
|
|
+ force_obj_index = get_manager().
|
|
|
|
|
+ register_global_obj<scalarxyz_obj>(force_output_name);
|
|
|
|
|
+ torque_obj_index = get_manager().
|
|
|
|
|
+ register_global_obj<scalarxyz_obj>(torque_output_name);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ default: {
|
|
|
|
|
+ assert(false);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // resolution
|
|
|
|
|
+ assert(config.contains("force_resolution"));
|
|
|
|
|
+ assert(config.contains("torque_resolution"));
|
|
|
|
|
+ assert(config["force_resolution"].is_number_float());
|
|
|
|
|
+ assert(config["torque_resolution"].is_number_float());
|
|
|
|
|
+ force_resolution = config["force_resolution"].get<double>();
|
|
|
|
|
+ torque_resolution = config["torque_resolution"].get<double>();
|
|
|
|
|
+
|
|
|
|
|
+ // report config
|
|
|
|
|
+ assert(config.contains("report_frequency"));
|
|
|
|
|
+ assert(config.contains("filter_type"));
|
|
|
|
|
+ assert(config["report_frequency"].is_number_unsigned());
|
|
|
|
|
+ assert(config["filter_type"].is_number_unsigned());
|
|
|
|
|
+ auto freq = config["report_frequency"].get<uint16_t>();
|
|
|
|
|
+ assert(freq >= 10);
|
|
|
|
|
+ auto filter = config["filter_type"].get<uint8_t>();
|
|
|
|
|
+ co_await send_config_message(1000 / freq, filter);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void setup_connection(const nlohmann::json &config) {
|
|
|
|
|
+ assert(conn == nullptr);
|
|
|
|
|
+ conn.reset(new serial_port(get_context()));
|
|
|
|
|
+
|
|
|
|
|
+ // open serial port
|
|
|
|
|
+ assert(config.contains("com_port"));
|
|
|
|
|
+ assert(config["com_port"].is_string());
|
|
|
|
|
+ auto com_port_name = config["com_port"].get<std::string>();
|
|
|
|
|
+ conn->open(com_port_name);
|
|
|
|
|
+ assert(conn->is_open());
|
|
|
|
|
+
|
|
|
|
|
+ // config
|
|
|
|
|
+ conn->set_option(serial_port::baud_rate(115200));
|
|
|
|
|
+ conn->set_option(serial_port::stop_bits(serial_port::stop_bits::one));
|
|
|
|
|
+ conn->set_option(serial_port::parity(serial_port::parity::none));
|
|
|
|
|
+ conn->set_option(serial_port::character_size(8));
|
|
|
|
|
+ conn->set_option(serial_port::flow_control(serial_port::flow_control::none));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void create_worker() {
|
|
|
|
|
+ auto exit_func = stop_on_exit_func(q_this);
|
|
|
|
|
+ switch (device_type) {
|
|
|
|
|
+ case 3: {
|
|
|
|
|
+ auto func_wrapper = make_noexcept_func(
|
|
|
|
|
+ std::bind(&impl::handle_type_3_message, this));
|
|
|
|
|
+ worker = make_infinite_coro_worker(
|
|
|
|
|
+ get_context(), std::move(func_wrapper), std::move(exit_func));
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ default: {
|
|
|
|
|
+ assert(false);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ worker->run();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ optoforce_daq::optoforce_daq()
|
|
|
|
|
+ : pimpl(std::make_unique<impl>()) {
|
|
|
|
|
+ pimpl->q_this = this;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ optoforce_daq::~optoforce_daq() = default;
|
|
|
|
|
+
|
|
|
|
|
+ awaitable<bool> optoforce_daq::on_init(const nlohmann::json &config) {
|
|
|
|
|
+ co_return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ awaitable<bool> optoforce_daq::on_start(const nlohmann::json &config) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ pimpl->setup_connection(config);
|
|
|
|
|
+ co_await pimpl->load_device_config(config);
|
|
|
|
|
+ pimpl->create_worker();
|
|
|
|
|
+ } catch (std::exception &e) {
|
|
|
|
|
+ // TODO show log
|
|
|
|
|
+ co_return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ co_return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ awaitable<void> optoforce_daq::on_stop() {
|
|
|
|
|
+ pimpl->worker->cancel();
|
|
|
|
|
+ co_await pimpl->worker->coro_wait_stop();
|
|
|
|
|
+ pimpl->worker = nullptr;
|
|
|
|
|
+ co_return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ awaitable<void> optoforce_daq::on_reset() {
|
|
|
|
|
+ co_return;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|