From 4bf7e987a56d3a505f772ea24a0e4176fd27dcd5 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sun, 13 May 2018 18:44:49 -0700 Subject: [PATCH] apply ODrive native protocol updates to fibre and switch to fibre --- Firmware/MotorControl/nvm_config.hpp | 7 +- Firmware/MotorControl/odrive_main.h | 2 +- Firmware/Tupfile.lua | 3 +- Firmware/communication/ascii_protocol.cpp | 13 +- .../{ascii_protocol.h => ascii_protocol.hpp} | 9 +- Firmware/communication/communication.cpp | 9 +- Firmware/communication/communication.h | 1 - Firmware/communication/crc.hpp | 66 -- Firmware/communication/interface_uart.cpp | 16 +- Firmware/communication/interface_usb.cpp | 6 +- Firmware/communication/protocol.cpp | 239 ----- Firmware/communication/protocol.hpp | 927 ------------------ Firmware/fibre/README.md | 2 +- .../fibre/cpp/include/fibre/cpp_utils.hpp | 39 +- Firmware/fibre/cpp/include/fibre/protocol.hpp | 316 ++++-- Firmware/fibre/cpp/protocol.cpp | 6 +- Firmware/fibre/test/test_server.cpp | 2 +- 17 files changed, 308 insertions(+), 1355 deletions(-) rename Firmware/communication/{ascii_protocol.h => ascii_protocol.hpp} (89%) delete mode 100644 Firmware/communication/crc.hpp delete mode 100644 Firmware/communication/protocol.cpp delete mode 100644 Firmware/communication/protocol.hpp diff --git a/Firmware/MotorControl/nvm_config.hpp b/Firmware/MotorControl/nvm_config.hpp index bf0f9134..2a96f595 100644 --- a/Firmware/MotorControl/nvm_config.hpp +++ b/Firmware/MotorControl/nvm_config.hpp @@ -12,11 +12,12 @@ #include #include "nvm.h" -#include +#include /* Private defines -----------------------------------------------------------*/ #define CONFIG_CRC16_INIT 0xabcd +#define CONFIG_CRC16_POLYNOMIAL 0x3d65 /* Private macros ------------------------------------------------------------*/ /* Private typedef -----------------------------------------------------------*/ @@ -76,7 +77,7 @@ struct Config { size_t previous_crc16 = *crc16; if (NVM_read(offset, (uint8_t *)val0, size)) return -1; - *crc16 = calc_crc16(previous_crc16, (uint8_t *)val0, size); + *crc16 = calc_crc16(previous_crc16, (uint8_t *)val0, size); if (Config::load_config(offset + size, crc16, vals...)) return -1; return 0; @@ -94,7 +95,7 @@ struct Config { return -1; // update CRC _after_ writing (in case val0 and crc16 point to the same address) if (crc16) - *crc16 = calc_crc16(*crc16, (uint8_t *)val0, size); + *crc16 = calc_crc16(*crc16, (uint8_t *)val0, size); if (Config::store_config(offset + size, crc16, vals...)) return -1; return 0; diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 2b29b6ac..9c134128 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -88,7 +88,7 @@ inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast(~static_c // ODrive specific includes -#include +#include #include #include #include diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index ff95f0d1..10f8e913 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -160,14 +160,15 @@ build{ 'MotorControl/main.cpp', 'communication/communication.cpp', 'communication/ascii_protocol.cpp', - 'communication/protocol.cpp', 'communication/interface_uart.cpp', 'communication/interface_usb.cpp', + 'fibre-cpp/protocol.cpp', 'FreeRTOS-openocd.c' }, includes={ 'Drivers/DRV8301', 'MotorControl', + 'fibre/cpp/include', '.' } } diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 98f1cd02..fbac6647 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -9,8 +9,9 @@ #include "odrive_main.h" #include "communication.h" -#include "ascii_protocol.h" +#include "ascii_protocol.hpp" #include +#include /* Private macros ------------------------------------------------------------*/ /* Private typedef -----------------------------------------------------------*/ @@ -31,15 +32,15 @@ template void respond(StreamSink& output, bool include_checksum, const char * fmt, TArgs&& ... args) { char response[64]; size_t len = snprintf(response, sizeof(response), fmt, std::forward(args)...); - output.process_bytes((uint8_t*)response, len); + output.process_bytes((uint8_t*)response, len, nullptr); // TODO: use process_all instead if (include_checksum) { uint8_t checksum = 0; for (size_t i = 0; i < len; ++i) checksum ^= response[i]; len = snprintf(response, sizeof(response), "*%u", checksum); - output.process_bytes((uint8_t*)response, len); + output.process_bytes((uint8_t*)response, len, nullptr); } - output.process_bytes((const uint8_t*)"\r\n", 2); + output.process_bytes((const uint8_t*)"\r\n", 2, nullptr); } @@ -139,7 +140,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& if (numscan < 1) { respond(response_channel, use_checksum, "invalid command format"); } else { - Endpoint* endpoint = application_endpoints->get_by_name(name, sizeof(name)); + Endpoint* endpoint = application_endpoints_->get_by_name(name, sizeof(name)); if (!endpoint) { respond(response_channel, use_checksum, "invalid property"); } else { @@ -158,7 +159,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& if (numscan < 1) { respond(response_channel, use_checksum, "invalid command format"); } else { - Endpoint* endpoint = application_endpoints->get_by_name(name, sizeof(name)); + Endpoint* endpoint = application_endpoints_->get_by_name(name, sizeof(name)); if (!endpoint) { respond(response_channel, use_checksum, "invalid property"); } else { diff --git a/Firmware/communication/ascii_protocol.h b/Firmware/communication/ascii_protocol.hpp similarity index 89% rename from Firmware/communication/ascii_protocol.h rename to Firmware/communication/ascii_protocol.hpp index 82830e10..145ee5e5 100644 --- a/Firmware/communication/ascii_protocol.h +++ b/Firmware/communication/ascii_protocol.hpp @@ -1,13 +1,9 @@ #ifndef __ASCII_PROTOCOL_H #define __ASCII_PROTOCOL_H -#ifdef __cplusplus -extern "C" { -#endif /* Includes ------------------------------------------------------------------*/ - -#include "protocol.hpp" +#include #include #include @@ -22,8 +18,5 @@ extern "C" { /* Exported functions --------------------------------------------------------*/ void ASCII_protocol_parse_stream(const uint8_t* buffer, size_t len, StreamSink& response_channel); -#ifdef __cplusplus -} -#endif #endif /* __ASCII_PROTOCOL_H */ diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 1c4accfd..eff82f74 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -7,7 +7,6 @@ #include "interface_uart.h" #include "odrive_main.h" -#include "protocol.hpp" #include "freertos_vars.h" #include "utils.h" @@ -149,11 +148,6 @@ static inline auto make_obj_tree() { using tree_type = decltype(make_obj_tree()); uint8_t tree_buffer[sizeof(tree_type)]; -// the protocol has one additional built-in endpoint -constexpr size_t MAX_ENDPOINTS = decltype(make_obj_tree())::endpoint_count + 1; -Endpoint* endpoints_[MAX_ENDPOINTS] = { 0 }; -const size_t max_endpoints_ = MAX_ENDPOINTS; -size_t n_endpoints_ = 0; // Thread to handle deffered processing of USB interrupt, and // read commands out of the UART DMA circular buffer @@ -164,8 +158,7 @@ void communication_task(void * ctx) { // the compiler uses the copy-constructor instead. Thus the make_obj_tree // ends up with a stupid stack size of around 8000 bytes. Fix this. auto tree_ptr = new (tree_buffer) tree_type(make_obj_tree()); - auto endpoint_provider = EndpointProvider_from_MemberList(*tree_ptr); - set_application_endpoints(&endpoint_provider); + fibre_publish(*tree_ptr); serve_on_uart(); serve_on_usb(); diff --git a/Firmware/communication/communication.h b/Firmware/communication/communication.h index 03da784b..8b38e6b4 100644 --- a/Firmware/communication/communication.h +++ b/Firmware/communication/communication.h @@ -8,7 +8,6 @@ #include #include -#include "crc.hpp" extern "C" { #endif diff --git a/Firmware/communication/crc.hpp b/Firmware/communication/crc.hpp deleted file mode 100644 index ce7de238..00000000 --- a/Firmware/communication/crc.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef __CRC_HPP -#define __CRC_HPP - -#include - -// Default CRC-8 Polynomial: x^8 + x^5 + x^4 + x^2 + x + 1 -// Can protect a 4 byte payload against toggling of up to 5 bits -// source: https://users.ece.cmu.edu/~koopman/crc/index.html -constexpr uint8_t CRC8_DEFAULT = 0x37; - -// Default CRC-16 Polynomial: 0x9eb2 x^16 + x^13 + x^12 + x^11 + x^10 + x^8 + x^6 + x^5 + x^2 + 1 -// Can protect a 135 byte payload against toggling of up to 5 bits -// source: https://users.ece.cmu.edu/~koopman/crc/index.html -// Also known as CRC-16-DNP -constexpr uint16_t CRC16_DEFAULT = 0x3d65; - -// Calculates an arbitrary CRC for one byte. -// Adapted from https://barrgroup.com/Embedded-Systems/How-To/CRC-Calculation-C-Code -template -static T calc_crc(T remainder, uint8_t value) { - constexpr T BIT_WIDTH = (CHAR_BIT * sizeof(T)); - constexpr T TOPBIT = ((T)1 << (BIT_WIDTH - 1)); - - // Bring the next byte into the remainder. - remainder ^= (value << (BIT_WIDTH - 8)); - - // Perform modulo-2 division, a bit at a time. - for (uint8_t bit = 8; bit; --bit) { - if (remainder & TOPBIT) { - remainder = (remainder << 1) ^ POLYNOMIAL; - } else { - remainder = (remainder << 1); - } - } - - return remainder; -} - -template -static T calc_crc(T remainder, const uint8_t* buffer, size_t length) { - while (length--) - remainder = calc_crc(remainder, *(buffer++)); - return remainder; -} - -template -static uint8_t calc_crc8(uint8_t remainder, uint8_t value) { - return calc_crc(remainder, value); -} - -template -static uint16_t calc_crc16(uint16_t remainder, uint8_t value) { - return calc_crc(remainder, value); -} - -template -static uint8_t calc_crc8(uint8_t remainder, const uint8_t* buffer, size_t length) { - return calc_crc(remainder, buffer, length); -} - -template -static uint16_t calc_crc16(uint16_t remainder, const uint8_t* buffer, size_t length) { - return calc_crc(remainder, buffer, length); -} - -#endif /* __CRC_HPP */ diff --git a/Firmware/communication/interface_uart.cpp b/Firmware/communication/interface_uart.cpp index 90d976aa..255ccb10 100644 --- a/Firmware/communication/interface_uart.cpp +++ b/Firmware/communication/interface_uart.cpp @@ -1,11 +1,11 @@ #include "interface_uart.h" -#include "protocol.hpp" -#include "ascii_protocol.h" +#include "ascii_protocol.hpp" #include +#include #include #include #include @@ -26,7 +26,7 @@ osThreadId uart_thread; class UART4Sender : public StreamSink { public: - int process_bytes(const uint8_t* buffer, size_t length) { + int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) { // Loop to ensure all bytes get sent while (length) { size_t chunk = length < UART_TX_BUFFER_SIZE ? length : UART_TX_BUFFER_SIZE; @@ -40,6 +40,8 @@ public: return -1; buffer += chunk; length -= chunk; + if (processed_bytes) + *processed_bytes += chunk; } return 0; } @@ -49,9 +51,9 @@ private: uint8_t tx_buf_[UART_TX_BUFFER_SIZE]; } uart4_stream_output; -PacketToStreamConverter uart4_packet_output(uart4_stream_output); +StreamBasedPacketSink uart4_packet_output(uart4_stream_output); BidirectionalPacketBasedChannel uart4_channel(uart4_packet_output); -StreamToPacketConverter uart4_stream_input(uart4_channel); +StreamToPacketSegmenter uart4_stream_input(uart4_channel); static void uart_server_thread(void * ctx) { (void) ctx; @@ -69,14 +71,14 @@ static void uart_server_thread(void * ctx) { // Process bytes in one or two chunks (two in case there was a wrap) if (new_rcv_idx < dma_last_rcv_idx) { uart4_stream_input.process_bytes(dma_rx_buffer + dma_last_rcv_idx, - UART_RX_BUFFER_SIZE - dma_last_rcv_idx); + UART_RX_BUFFER_SIZE - dma_last_rcv_idx, nullptr); // TODO: use process_all ASCII_protocol_parse_stream(dma_rx_buffer + dma_last_rcv_idx, UART_RX_BUFFER_SIZE - dma_last_rcv_idx, uart4_stream_output); dma_last_rcv_idx = 0; } if (new_rcv_idx > dma_last_rcv_idx) { uart4_stream_input.process_bytes(dma_rx_buffer + dma_last_rcv_idx, - new_rcv_idx - dma_last_rcv_idx); + new_rcv_idx - dma_last_rcv_idx, nullptr); // TODO: use process_all ASCII_protocol_parse_stream(dma_rx_buffer + dma_last_rcv_idx, new_rcv_idx - dma_last_rcv_idx, uart4_stream_output); dma_last_rcv_idx = new_rcv_idx; diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index cee94a99..decf45d2 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -1,9 +1,9 @@ #include "interface_usb.h" -#include "protocol.hpp" #include +#include #include #include #include @@ -51,7 +51,7 @@ public: class TreatPacketSinkAsStreamSink : public StreamSink { public: TreatPacketSinkAsStreamSink(PacketSink& output) : output_(output) {} - int process_bytes(const uint8_t* buffer, size_t length) { + int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) { // Loop to ensure all bytes get sent while (length) { size_t chunk = length < USB_TX_DATA_SIZE ? length : USB_TX_DATA_SIZE; @@ -59,6 +59,8 @@ public: return -1; buffer += chunk; length -= chunk; + if (processed_bytes) + *processed_bytes += chunk; } return 0; } diff --git a/Firmware/communication/protocol.cpp b/Firmware/communication/protocol.cpp deleted file mode 100644 index 50e38fab..00000000 --- a/Firmware/communication/protocol.cpp +++ /dev/null @@ -1,239 +0,0 @@ - -/* Includes ------------------------------------------------------------------*/ - -//#include "low_level.h" -#include "protocol.hpp" - -#include -#include - -/* Private defines -----------------------------------------------------------*/ -/* Private macros ------------------------------------------------------------*/ -/* Private typedef -----------------------------------------------------------*/ -/* Global constant data ------------------------------------------------------*/ -/* Global variables ----------------------------------------------------------*/ -/* Private constant data -----------------------------------------------------*/ -/* Private variables ---------------------------------------------------------*/ -/* Private function prototypes -----------------------------------------------*/ - -static void hexdump(const uint8_t* buf, size_t len); -static inline int write_string(const char* str, StreamSink* output); - -/* Function implementations --------------------------------------------------*/ - -#if 0 -void hexdump(const uint8_t* buf, size_t len) { - for (size_t pos = 0; pos < len; ++pos) { - printf(" %02x", buf[pos]); - if ((((pos + 1) % 16) == 0) || ((pos + 1) == len)) - printf("\r\n"); - osDelay(2); - } -} -#else -void hexdump(const uint8_t* buf, size_t len) { - (void) buf; - (void) len; -} -#endif - - - -int StreamToPacketConverter::process_bytes(const uint8_t *buffer, size_t length) { - int result = 0; - - while (length--) { - if (header_index_ < sizeof(header_buffer_)) { - // Process header byte - header_buffer_[header_index_++] = *buffer; - if (header_index_ == 1 && header_buffer_[0] != SYNC_BYTE) { - header_index_ = 0; - } else if (header_index_ == 2 && (header_buffer_[1] & 0x80)) { - header_index_ = 0; // TODO: support packets larger than 128 bytes - } else if (header_index_ == 3 && calc_crc8(CRC8_INIT, header_buffer_, 3)) { - header_index_ = 0; - } else if (header_index_ == 3) { - packet_length_ = header_buffer_[1] + 2; - } - } else if (packet_index_ < sizeof(packet_buffer_)) { - // Process payload byte - packet_buffer_[packet_index_++] = *buffer; - } - - // If both header and packet are fully received, hand it on to the packet processor - if (header_index_ == 3 && packet_index_ == packet_length_) { - if (calc_crc16(CRC16_INIT, packet_buffer_, packet_length_) == 0) { - result |= output_.process_packet(packet_buffer_, packet_length_ - 2); - } - header_index_ = packet_index_ = packet_length_ = 0; - } - buffer++; - } - - return result; -} - -int PacketToStreamConverter::process_packet(const uint8_t *buffer, size_t length) { - // TODO: support buffer size >= 128 - if (length >= 128) - return -1; - - LOG_PROTO("send header\r\n"); - uint8_t header[] = { - SYNC_BYTE, - static_cast(length), - 0 - }; - header[2] = calc_crc8(CRC8_INIT, header, 2); - - if (output_.process_bytes(header, sizeof(header))) - return -1; - LOG_PROTO("send payload:\r\n"); - hexdump(buffer, length); - if (output_.process_bytes(buffer, length)) - return -1; - - LOG_PROTO("send crc16\r\n"); - uint16_t crc16 = calc_crc16(CRC16_INIT, buffer, length); - uint8_t crc16_buffer[] = { - (uint8_t)((crc16 >> 8) & 0xff), - (uint8_t)((crc16 >> 0) & 0xff) - }; - if (output_.process_bytes(crc16_buffer, 2)) - return -1; - LOG_PROTO("sent!\r\n"); - return 0; -} - - -class JSONDescriptorEndpoint : Endpoint { -public: - static constexpr size_t endpoint_count = 1; - void write_json(size_t id, StreamSink* output); - void register_endpoints(Endpoint** list, size_t id, size_t length); - void handle(const uint8_t* input, size_t input_length, StreamSink* output); -}; - -JSONDescriptorEndpoint json_file_endpoint = JSONDescriptorEndpoint(); -EndpointProvider* application_endpoints; -uint16_t json_crc_; - -void JSONDescriptorEndpoint::write_json(size_t id, StreamSink* output) { - write_string("{\"name\":\"\",", output); - - // write endpoint ID - write_string("\"id\":", output); - char id_buf[10]; - snprintf(id_buf, sizeof(id_buf), "%u", id); // TODO: get rid of printf - write_string(id_buf, output); - - write_string(",\"type\":\"json\",\"access\":\"r\"}", output); -} - -void JSONDescriptorEndpoint::register_endpoints(Endpoint** list, size_t id, size_t length) { - if (id < length) - list[id] = this; - -}; - -// Returns part of the JSON interface definition. -void JSONDescriptorEndpoint::handle(const uint8_t* input, size_t input_length, StreamSink* output) { - // The request must contain a 32 bit integer to specify an offset - if (input_length < 4) - return; - uint32_t offset = 0; - read_le(&offset, input); - NullStreamSink output_with_offset = NullStreamSink(offset, *output); - - size_t id = 0; - write_string("[", &output_with_offset); - json_file_endpoint.write_json(id, &output_with_offset); - id += decltype(json_file_endpoint)::endpoint_count; - write_string(",", &output_with_offset); - application_endpoints->write_json(id, &output_with_offset); - write_string("]", &output_with_offset); -} - -void set_application_endpoints(EndpointProvider* endpoints) { - application_endpoints = endpoints; - - n_endpoints_ = 0; - json_file_endpoint.register_endpoints(endpoints_, 0, max_endpoints_); - n_endpoints_ += decltype(json_file_endpoint)::endpoint_count; - application_endpoints->register_endpoints(endpoints_, n_endpoints_, max_endpoints_); - n_endpoints_ += application_endpoints->get_endpoint_count(); - - // Calculates the CRC16 of the JSON file. - // The init value is the protocol version. - CRC16Calculator crc16_calculator(PROTOCOL_VERSION); - uint8_t offset[4] = { 0 }; - json_file_endpoint.handle(offset, sizeof(offset), &crc16_calculator); - json_crc_ = crc16_calculator.get_crc16(); - - CRC16Calculator crc16_calculator2(PROTOCOL_VERSION); - endpoints_[0]->handle(offset, sizeof(offset), &crc16_calculator2); - json_crc_ = crc16_calculator2.get_crc16(); -} - -int BidirectionalPacketBasedChannel::process_packet(const uint8_t* buffer, size_t length) { - LOG_PROTO("got packet of length %d: \r\n", length); - hexdump(buffer, length); - if (length < 4) - return -1; - - uint16_t seq_no = read_le(&buffer, &length); - - if (seq_no & 0x8000) { - // TODO: ack handling - } else { - // TODO: think about some kind of ordering guarantees - // currently the seq_no is just used to associate a response with a request - - uint16_t endpoint_id = read_le(&buffer, &length); - bool expect_response = endpoint_id & 0x8000; - endpoint_id &= 0x7fff; - - if (endpoint_id >= n_endpoints_) - return -1; - - Endpoint* endpoint = endpoints_[endpoint_id]; - if (!endpoint) { - LOG_PROTO("critical: no endpoint at %d", endpoint_id); - return -1; - } - - // Verify packet trailer. The expected trailer value depends on the selected endpoint. - // For endpoint 0 this is just the protocol version, for all other endpoints it's a - // CRC over the entire JSON descriptor tree (this may change in future versions). - uint16_t expected_trailer = endpoint_id ? json_crc_ : PROTOCOL_VERSION; - uint16_t actual_trailer = buffer[length - 2] | (buffer[length - 1] << 8); - if (expected_trailer != actual_trailer) { - LOG_PROTO("trailer mismatch for endpoint %d: expected %04x, got %04x\r\n", endpoint_id, expected_trailer, actual_trailer); - return -1; - } - LOG_PROTO("trailer ok for endpoint %d\r\n", endpoint_id); - - // TODO: if more bytes than the MTU were requested, should we abort or just return as much as possible? - - uint16_t expected_response_length = read_le(&buffer, &length); - - // Limit response length according to our local TX buffer size - if (expected_response_length > sizeof(tx_buf_) - 2) - expected_response_length = sizeof(tx_buf_) - 2; - - MemoryStreamSink output(tx_buf_ + 2, expected_response_length); - endpoint->handle(buffer, length - 2, &output); - - // Send response - if (expect_response) { - size_t actual_response_length = expected_response_length - output.get_free_space() + 2; - write_le(seq_no | 0x8000, tx_buf_); - - LOG_PROTO("send packet:\r\n"); - hexdump(tx_buf_, actual_response_length); - output_.process_packet(tx_buf_, actual_response_length); - } - } - - return 0; -} diff --git a/Firmware/communication/protocol.hpp b/Firmware/communication/protocol.hpp deleted file mode 100644 index 87ef82cc..00000000 --- a/Firmware/communication/protocol.hpp +++ /dev/null @@ -1,927 +0,0 @@ -/* -see protocol.md for the protocol specification -*/ - -#ifndef __PROTOCOL_HPP -#define __PROTOCOL_HPP - -// TODO: resolve assert -#define assert(expr) - -#include -#include -#include -#include "crc.hpp" - -// Note that this option cannot be used to debug UART because it prints on UART -//#define DEBUG_PROTOCOL -#ifdef DEBUG_PROTOCOL -#define LOG_PROTO(...) do { printf(__VA_ARGS__); osDelay(10); } while (0) -#else -#define LOG_PROTO(...) ((void) 0) -#endif - - -constexpr uint8_t SYNC_BYTE = 0xAA; -constexpr uint8_t CRC8_INIT = 0x42; -constexpr uint16_t CRC16_INIT = 0x1337; -constexpr uint16_t PROTOCOL_VERSION = 1; - -// This value must not be larger than USB_TX_DATA_SIZE defined in usbd_cdc_if.h -//Oskar: What's the error? What values work? Does 63 work? Ideally we figure out how to get 64 to work, but if not let's find something better than 32. -constexpr uint16_t TX_BUF_SIZE = 32; // does not work with 64 for some reason -constexpr uint16_t RX_BUF_SIZE = 128; // larger values than 128 have currently no effect because of protocol limitations - -// Maximum time we allocate for processing and responding to a request -constexpr uint32_t PROTOCOL_SERVER_TIMEOUT_MS = 10; - -template -inline size_t write_le(T value, uint8_t* buffer); - -template -inline size_t read_le(T* value, const uint8_t* buffer); - -template<> -inline size_t write_le(bool value, uint8_t* buffer) { - buffer[0] = value ? 1 : 0; - return 1; -} - -template<> -inline size_t write_le(uint8_t value, uint8_t* buffer) { - buffer[0] = value; - return 1; -} - -template<> -inline size_t write_le(uint16_t value, uint8_t* buffer) { - buffer[0] = (value >> 0) & 0xff; - buffer[1] = (value >> 8) & 0xff; - return 2; -} - -template<> -inline size_t write_le(uint32_t value, uint8_t* buffer) { - buffer[0] = (value >> 0) & 0xff; - buffer[1] = (value >> 8) & 0xff; - buffer[2] = (value >> 16) & 0xff; - buffer[3] = (value >> 24) & 0xff; - return 4; -} - -template<> -inline size_t write_le(int32_t value, uint8_t* buffer) { - buffer[0] = (value >> 0) & 0xff; - buffer[1] = (value >> 8) & 0xff; - buffer[2] = (value >> 16) & 0xff; - buffer[3] = (value >> 24) & 0xff; - return 4; -} - -template<> -inline size_t write_le(uint64_t value, uint8_t* buffer) { - buffer[0] = (value >> 0) & 0xff; - buffer[1] = (value >> 8) & 0xff; - buffer[2] = (value >> 16) & 0xff; - buffer[3] = (value >> 24) & 0xff; - buffer[4] = (value >> 32) & 0xff; - buffer[5] = (value >> 40) & 0xff; - buffer[6] = (value >> 48) & 0xff; - buffer[7] = (value >> 56) & 0xff; - return 8; -} - -template<> -inline size_t write_le(float value, uint8_t* buffer) { - static_assert(CHAR_BIT * sizeof(float) == 32, "32 bit floating point expected"); - static_assert(std::numeric_limits::is_iec559, "IEEE 754 floating point expected"); - const uint32_t * value_as_uint32 = reinterpret_cast(&value); - return write_le(*value_as_uint32, buffer); -} - -template<> -inline size_t read_le(bool* value, const uint8_t* buffer) { - *value = buffer[0]; - return 1; -} - -template<> -inline size_t read_le(uint8_t* value, const uint8_t* buffer) { - *value = buffer[0]; - return 1; -} - -template<> -inline size_t read_le(uint16_t* value, const uint8_t* buffer) { - *value = (static_cast(buffer[0]) << 0) | - (static_cast(buffer[1]) << 8); - return 2; -} - -template<> -inline size_t read_le(int32_t* value, const uint8_t* buffer) { - *value = (static_cast(buffer[0]) << 0) | - (static_cast(buffer[1]) << 8) | - (static_cast(buffer[2]) << 16) | - (static_cast(buffer[3]) << 24); - return 4; -} - -template<> -inline size_t read_le(uint32_t* value, const uint8_t* buffer) { - *value = (static_cast(buffer[0]) << 0) | - (static_cast(buffer[1]) << 8) | - (static_cast(buffer[2]) << 16) | - (static_cast(buffer[3]) << 24); - return 4; -} - -template<> -inline size_t read_le(uint64_t* value, const uint8_t* buffer) { - *value = (static_cast(buffer[0]) << 0) | - (static_cast(buffer[1]) << 8) | - (static_cast(buffer[2]) << 16) | - (static_cast(buffer[3]) << 24) | - (static_cast(buffer[4]) << 32) | - (static_cast(buffer[5]) << 40) | - (static_cast(buffer[6]) << 48) | - (static_cast(buffer[7]) << 56); - return 8; -} - -template<> -inline size_t read_le(float* value, const uint8_t* buffer) { - static_assert(CHAR_BIT * sizeof(float) == 32, "32 bit floating point expected"); - static_assert(std::numeric_limits::is_iec559, "IEEE 754 floating point expected"); - return read_le(reinterpret_cast(value), buffer); -} - -// @brief Reads a value of type T from the buffer. -// @param buffer Pointer to the buffer to be read. The pointer is updated by the number of bytes that were read. -// @param length The number of available bytes in buffer. This value is updated to subtract the bytes that were read. -template -static inline T read_le(const uint8_t** buffer, size_t* length) { - T result; - size_t cnt = read_le(&result, *buffer); - *buffer += cnt; - *length -= cnt; - return result; -} - -class PacketSink { -public: - // @brief Processes a packet. - // The blocking behavior shall depend on the thread-local deadline_ms variable. - // @return: 0 on success, otherwise a non-zero error code - // TODO: define what happens when the packet is larger than what the implementation can handle. - virtual int process_packet(const uint8_t* buffer, size_t length) = 0; -}; - -class StreamSink { -public: - // @brief Processes a chunk of bytes that is part of a continuous stream. - // The blocking behavior shall depend on the thread-local deadline_ms variable. - // @return: 0 on success, otherwise a non-zero error code - virtual int process_bytes(const uint8_t* buffer, size_t length) = 0; - - // @brief Returns the number of bytes that can still be written to the stream. - // Shall return SIZE_MAX if the stream has unlimited lenght. - virtual size_t get_free_space() = 0; -}; - - -class StreamToPacketConverter : public StreamSink { -public: - StreamToPacketConverter(PacketSink& output) : - output_(output) - { - }; - - int process_bytes(const uint8_t *buffer, size_t length); - - size_t get_free_space() { return SIZE_MAX; } - -private: - uint8_t header_buffer_[3]; - size_t header_index_ = 0; - uint8_t packet_buffer_[RX_BUF_SIZE]; - size_t packet_index_ = 0; - size_t packet_length_ = 0; - PacketSink& output_; -}; - - -class PacketToStreamConverter : public PacketSink { -public: - PacketToStreamConverter(StreamSink& output) : - output_(output) - { - }; - - int process_packet(const uint8_t *buffer, size_t length); - -private: - StreamSink& output_; -}; - - -// Implements the StreamSink interface by writing into a fixed size -// memory buffer. -class MemoryStreamSink : public StreamSink { -public: - MemoryStreamSink(uint8_t *buffer, size_t length) : - buffer_(buffer), - buffer_length_(length) {} - - // Returns 0 on success and -1 if the buffer could not accept everything because it became full - int process_bytes(const uint8_t* buffer, size_t length) { - int status = 0; - if (length > buffer_length_) { - length = buffer_length_; - status = -1; - } - memcpy(buffer_, buffer, length); - buffer_ += length; - buffer_length_ -= length; - return status; - } - - size_t get_free_space() { return buffer_length_; } - -private: - uint8_t * buffer_; - size_t buffer_length_; -}; - -// Implements the StreamSink interface by discarding the first couple of bytes -// and then forwarding the rest to another stream. -class NullStreamSink : public StreamSink { -public: - NullStreamSink(size_t skip, StreamSink& follow_up_stream) : - skip_(skip), - follow_up_stream_(follow_up_stream) {} - - // Returns 0 on success and -1 if the buffer could not accept everything because it became full - int process_bytes(const uint8_t* buffer, size_t length) { - if (skip_ < length) { - buffer += skip_; - length -= skip_; - skip_ = 0; - return follow_up_stream_.process_bytes(buffer, length); - } else { - skip_ -= length; - return 0; - } - } - - size_t get_free_space() { return skip_ + follow_up_stream_.get_free_space(); } - -private: - size_t skip_; - StreamSink& follow_up_stream_; -}; - - - -// Implements the StreamSink interface by calculating the CRC16 checksum -// on the data that is sent to it. -class CRC16Calculator : public StreamSink { -public: - CRC16Calculator(uint16_t crc16_init) : - crc16_(crc16_init) {} - - int process_bytes(const uint8_t* buffer, size_t length) { - crc16_ = calc_crc16(crc16_, buffer, length); - return 0; - } - - size_t get_free_space() { return SIZE_MAX; } - - uint16_t get_crc16() { return crc16_; } -private: - uint16_t crc16_; -}; - - -// @brief Endpoint request handler -// -// When passed a valid endpoint context, implementing functions shall handle an -// endpoint read/write request by reading the provided input data and filling in -// output data. The exact semantics of this function depends on the corresponding -// endpoint's specification. -// -// @param input: pointer to the input data -// @param input_length: number of available input bytes -// @param output: The stream where to write the output to. Can be null. -// The handler shall abort as soon as the stream returns -// a non-zero error code on write. -typedef std::function EndpointHandler; - - -template -void default_readwrite_endpoint_handler(const T* value, const uint8_t* input, size_t input_length, StreamSink* output) { - // If the old value was requested, call the corresponding little endian serialization function - if (output) { - // TODO: make buffer size dependent on the type - uint8_t buffer[sizeof(T)]; - size_t cnt = write_le(*value, buffer); - if (cnt <= output->get_free_space()) - output->process_bytes(buffer, cnt); - } -} - -template -void default_readwrite_endpoint_handler(T* value, const uint8_t* input, size_t input_length, StreamSink* output) { - // Read the endpoint value into output - default_readwrite_endpoint_handler(const_cast(value), input, input_length, output); - - // If a new value was passed, call the corresponding little endian deserialization function - uint8_t buffer[sizeof(T)] = { 0 }; // TODO: make buffer size dependent on the type - if (input_length >= sizeof(buffer)) - read_le(value, input); -} - - - -template -static inline const char* get_default_json_modifier(); - -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"float\",\"access\":\"r\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"float\",\"access\":\"rw\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint64\",\"access\":\"r\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint64\",\"access\":\"rw\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"int32\",\"access\":\"r\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"int32\",\"access\":\"rw\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint32\",\"access\":\"r\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint32\",\"access\":\"rw\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint16\",\"access\":\"r\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint16\",\"access\":\"rw\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint8\",\"access\":\"r\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"uint8\",\"access\":\"rw\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"bool\",\"access\":\"r\""; -} -template<> -inline constexpr const char* get_default_json_modifier() { - return "\"type\":\"bool\",\"access\":\"rw\""; -} - -class Endpoint { -public: - //const char* const name_; - virtual void handle(const uint8_t* input, size_t input_length, StreamSink* output) = 0; - virtual bool get_string(char * output, size_t length) { return false; }; - virtual bool set_string(char * buffer, size_t length) { return false; } -}; - -class EndpointProvider { -public: - virtual size_t get_endpoint_count() = 0; - virtual void write_json(size_t id, StreamSink* output) = 0; - virtual Endpoint* get_by_name(char * name, size_t length) = 0; - virtual void register_endpoints(Endpoint** list, size_t id, size_t length) = 0; -}; - - -static inline int write_string(const char* str, StreamSink* output) { - return output->process_bytes(reinterpret_cast(str), strlen(str)); -} - - -/* @brief Handles the communication protocol on one channel. -* -* When instantiated with a list of endpoints and an output packet sink, -* objects of this class will handle packets passed into process_packet, -* pass the relevant data to the corresponding endpoints and dispatch response -* packets on the output. -*/ -class BidirectionalPacketBasedChannel : public PacketSink { -public: - BidirectionalPacketBasedChannel(PacketSink& output) : - output_(output) - { } - - int process_packet(const uint8_t* buffer, size_t length); -private: - PacketSink& output_; - uint8_t tx_buf_[TX_BUF_SIZE]; -}; - - -template -struct MemberList; - -template<> -struct MemberList<> { -public: - static constexpr size_t endpoint_count = 0; - static constexpr bool is_empty = true; - void write_json(size_t id, StreamSink* output) { - // no action - } - void register_endpoints(Endpoint** list, size_t id, size_t length) { - // no action - } - Endpoint* get_by_name(const char * name, size_t length) { - return nullptr; - } - std::tuple<> get_names_as_tuple() const { return std::tuple<>(); } -}; - -template -struct MemberList { -public: - static constexpr size_t endpoint_count = TMember::endpoint_count + MemberList::endpoint_count; - static constexpr bool is_empty = false; - - MemberList(TMember&& this_member, TMembers&&... subsequent_members) : - this_member_(std::forward(this_member)), - subsequent_members_(std::forward(subsequent_members)...) {} - - MemberList(TMember&& this_member, MemberList&& subsequent_members) : - this_member_(std::forward(this_member)), - subsequent_members_(std::forward>(subsequent_members)) {} - - // @brief Move constructor -/* MemberList(MemberList&& other) : - this_member_(std::move(other.this_member_)), - subsequent_members_(std::move(other.subsequent_members_)) {}*/ - - void write_json(size_t id, StreamSink* output) /*final*/ { - this_member_.write_json(id, output); - if (!MemberList::is_empty) - write_string(",", output); - subsequent_members_.write_json(id + TMember::endpoint_count, output); - } - - Endpoint* get_by_name(const char * name, size_t length) { - Endpoint* result = this_member_.get_by_name(name, length); - if (result) return result; - else return subsequent_members_.get_by_name(name, length); - } - - void register_endpoints(Endpoint** list, size_t id, size_t length) /*final*/ { - this_member_.register_endpoints(list, id, length); - subsequent_members_.register_endpoints(list, id + TMember::endpoint_count, length); - } - - TMember this_member_; - MemberList subsequent_members_; -}; - -template -MemberList make_protocol_member_list(TMembers&&... member_list) { - return MemberList(std::forward(member_list)...); -} - -template -class ProtocolObject { -public: - ProtocolObject(const char * name, TMembers&&... member_list) : - name_(name), - member_list_(std::forward(member_list)...) {} - - static constexpr size_t endpoint_count = MemberList::endpoint_count; - - void write_json(size_t id, StreamSink* output) { - write_string("{\"name\":\"", output); - write_string(name_, output); - write_string("\",\"type\":\"object\",\"members\":[", output); - member_list_.write_json(id, output), - write_string("]}", output); - } - - Endpoint* get_by_name(const char * name, size_t length) { - size_t segment_length = strlen(name); - if (!strncmp(name, name_, length)) - return member_list_.get_by_name(name + segment_length + 1, length - segment_length - 1); - else - return nullptr; - } - - void register_endpoints(Endpoint** list, size_t id, size_t length) { - member_list_.register_endpoints(list, id, length); - } - - const char * name_; - MemberList member_list_; -}; - -template -ProtocolObject make_protocol_object(const char * name, TMembers&&... member_list) { - return ProtocolObject(name, std::forward(member_list)...); -} - - -// TODO: move to cpp_utils -#define ENABLE_IF_SAME(a, b, type) \ - template typename std::enable_if_t::value, bool> - -template -class ProtocolProperty : public Endpoint { -public: - static constexpr const char * json_modifier = get_default_json_modifier(); - static constexpr size_t endpoint_count = 1; - - ProtocolProperty(const char * name, TProperty* property) - : name_(name), property_(property) - {} - -/* TODO: find out why the move constructor is not used when it could be - ProtocolProperty(const ProtocolProperty&) = delete; - // @brief Move constructor - ProtocolProperty(ProtocolProperty&& other) : - Endpoint(std::move(other)), - name_(std::move(other.name_)), - property_(other.property_) - {} - constexpr ProtocolProperty& operator=(const ProtocolProperty& other) = delete; - constexpr ProtocolProperty& operator=(const ProtocolProperty& other) { - //Endpoint(std::move(other)), - //name_(std::move(other.name_)), - //property_(other.property_) - name_ = other.name_; - property_ = other.property_; - return *this; - } - ProtocolProperty& operator=(ProtocolProperty&& other) - : name_(other.name_), property_(other.property_) - {} - ProtocolProperty& operator=(const ProtocolProperty& other) - : name_(other.name_), property_(other.property_) - {}*/ - - void write_json(size_t id, StreamSink* output) { - // write name - write_string("{\"name\":\"", output); - LOG_PROTO("json: this at %x, name at %x is s\r\n", (uintptr_t)this, (uintptr_t)name_); - //LOG_PROTO("json\r\n"); - write_string(name_, output); - - // write endpoint ID - write_string("\",\"id\":", output); - char id_buf[10]; - snprintf(id_buf, sizeof(id_buf), "%u", id); // TODO: get rid of printf - write_string(id_buf, output); - - // write additional JSON data - if (json_modifier && json_modifier[0]) { - write_string(",", output); - write_string(json_modifier, output); - } - - write_string("}", output); - } - - Endpoint* get_by_name(const char * name, size_t length) { - if (!strncmp(name, name_, length)) - return this; - else - return nullptr; - } - - - // *** ASCII protocol handlers *** - - ENABLE_IF_SAME(std::decay_t, float, bool) - get_string_ex(char * buffer, size_t length, int) { - snprintf(buffer, length, "%f", *property_); - return true; - } - ENABLE_IF_SAME(std::decay_t, int32_t, bool) - get_string_ex(char * buffer, size_t length, int) { - snprintf(buffer, length, "%ld", *property_); - return true; - } - ENABLE_IF_SAME(std::decay_t, uint32_t, bool) - get_string_ex(char * buffer, size_t length, int) { - snprintf(buffer, length, "%lu", *property_); - return true; - } - ENABLE_IF_SAME(std::decay_t, bool, bool) - get_string_ex(char * buffer, size_t length, int) { - buffer[0] = (*property_) ? '1' : '0'; - buffer[1] = 0; - return true; - } - bool get_string_ex(char * buffer, size_t length, ...) { - return false; - } - bool get_string(char * buffer, size_t length) final { - return get_string_ex(buffer, length, 0); - } - ENABLE_IF_SAME(TProperty, float, bool) - set_string_ex(char * buffer, size_t length, int) { - return sscanf(buffer, "%f", property_) == 1; - } - ENABLE_IF_SAME(TProperty, int32_t, bool) - set_string_ex(char * buffer, size_t length, int) { - return sscanf(buffer, "%ld", property_) == 1; - } - ENABLE_IF_SAME(TProperty, uint32_t, bool) - set_string_ex(char * buffer, size_t length, int) { - return sscanf(buffer, "%lu", property_) == 1; - } - ENABLE_IF_SAME(TProperty, bool, bool) - set_string_ex(char * buffer, size_t length, int) { - int val; - if (sscanf(buffer, "%d", &val) != 1) - return false; - *property_ = val; - return true; - } - bool set_string_ex(char * buffer, size_t length, ...) { - return false; - } - bool set_string(char * buffer, size_t length) final { - //__asm ("bkpt"); - return set_string_ex(buffer, length, 0); - } - - void register_endpoints(Endpoint** list, size_t id, size_t length) { - if (id < length) - list[id] = this; - } - void handle(const uint8_t* input, size_t input_length, StreamSink* output) { - default_readwrite_endpoint_handler(property_, input, input_length, output); - } - /*void handle(const uint8_t* input, size_t input_length, StreamSink* output) { - handle(input, input_length, output); - }*/ - - const char * name_; - TProperty* property_; -}; - -// Non-const non-enum types -template::value>> -ProtocolProperty make_protocol_property(const char * name, TProperty* property) { - return ProtocolProperty(name, property); -}; - -// Const non-enum types -template::value>> -ProtocolProperty make_protocol_ro_property(const char * name, const TProperty* property) { - return ProtocolProperty(name, property); -}; - -// Non-const enum types -template::value>> -ProtocolProperty> make_protocol_property(const char * name, TProperty* property) { - return ProtocolProperty>(name, reinterpret_cast*>(property)); -}; - -// Const enum types -template::value>> -ProtocolProperty> make_protocol_ro_property(const char * name, const TProperty* property) { - return ProtocolProperty>(name, reinterpret_cast*>(property)); -}; - - - -template -class FunctionTraits { -public: - template> - static TRet invoke(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple packed_args, TUnpackedArgs ... args) { - return invoke(obj, func_ptr, packed_args, args..., std::get(packed_args)); - } - - template - static TRet invoke(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple packed_args, TArgs ... args) { - return (obj.*func_ptr)(args...); - } -}; - -/* @brief Invoke a class member function with a variable number of arguments that are supplied as a tuple - -Example usage: - -class MyClass { -public: - int MyFunction(int a, int b) { - return 0; - } -}; - -MyClass my_object; -std::tuple my_args(3, 4); // arguments are supplied as a tuple -int result = invoke_function_with_tuple(my_object, &MyClass::MyFunction, my_args); -*/ -template -TRet invoke_function_with_tuple(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple packed_args) { - return FunctionTraits::template invoke<0>(obj, func_ptr, packed_args); -} - - -template -struct PropertyListFactory; - -template<> -struct PropertyListFactory<> { - template - static MemberList<> make_property_list(std::array names, std::tuple& values) { - return MemberList<>(); - } -}; - -template -struct PropertyListFactory { - template - static MemberList, ProtocolProperty...> - make_property_list(std::array names, std::tuple& values) { - return MemberList, ProtocolProperty...>( - make_protocol_property(std::get(names), &std::get(values)), - PropertyListFactory::template make_property_list(names, values) - ); - } -}; - - -template -struct return_type; - -template<> -struct return_type<> { typedef void type; }; -template -struct return_type { typedef T type; }; -template -struct return_type { typedef std::tuple type; }; - - - -template -class ProtocolFunction; - -template - //template typename asd, - //template typename ssss> -class ProtocolFunction, std::tuple> : Endpoint { -public: - - // @brief The return type of the function as written by a C++ programmer - using TRet = typename return_type::type; - - static constexpr size_t endpoint_count = 1 + MemberList...>::endpoint_count + MemberList...>::endpoint_count; - - ProtocolFunction(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TInputs...), - std::array input_names, - std::array output_names) : - name_(name), obj_(&obj), func_ptr_(func_ptr), - input_names_{input_names}, output_names_{output_names}, - input_properties_(PropertyListFactory::template make_property_list<0>(input_names_, in_args_)), - output_properties_(PropertyListFactory::template make_property_list<0>(output_names_, out_args_)) - { - LOG_PROTO("my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); - } - - void write_json(size_t id, StreamSink* output) { - // write name - write_string("{\"name\":\"", output); - write_string(name_, output); - - // write endpoint ID - write_string("\",\"id\":", output); - char id_buf[10]; - snprintf(id_buf, sizeof(id_buf), "%u", id); // TODO: get rid of printf - write_string(id_buf, output); - - // write arguments - write_string(",\"type\":\"function\",\"inputs\":[", output); - input_properties_.write_json(id + 1, output), - write_string("],\"outputs\":[", output); - output_properties_.write_json(id + 1 + decltype(input_properties_)::endpoint_count, output), - write_string("]}", output); - } - - Endpoint* get_by_name(const char * name, size_t length) { - return nullptr; // can't address functions by name - } - - void register_endpoints(Endpoint** list, size_t id, size_t length) { - if (id < length) - list[id] = this; - input_properties_.register_endpoints(list, id + 1, length); - output_properties_.register_endpoints(list, id + 1 + decltype(input_properties_)::endpoint_count, length); - } - - template std::enable_if_t - handle_ex() { - invoke_function_with_tuple(*obj_, func_ptr_, in_args_); - } - - template std::enable_if_t - handle_ex() { - std::get<0>(out_args_) = invoke_function_with_tuple(*obj_, func_ptr_, in_args_); - } - - template std::enable_if_t= 2> - handle_ex() { - out_args_ = invoke_function_with_tuple(*obj_, func_ptr_, in_args_); - } - - void handle(const uint8_t* input, size_t input_length, StreamSink* output) { - (void) input; - (void) input_length; - (void) output; - LOG_PROTO("tuple still at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); - LOG_PROTO("invoke function using %d and %.3f\r\n", std::get<0>(in_args_), std::get<1>(in_args_)); - handle_ex(); - } - - const char * name_; - TObj* obj_; - TRet(TObj::*func_ptr_)(TInputs...); - std::array input_names_; // TODO: remove - std::array output_names_; // TODO: remove - std::tuple in_args_; - std::tuple out_args_; - MemberList...> input_properties_; - MemberList...> output_properties_; -}; - -template> -ProtocolFunction, std::tuple<>> make_protocol_function(const char * name, TObj& obj, void(TObj::*func_ptr)(TArgs...), TNames ... names) { - return ProtocolFunction, std::tuple<>>(name, obj, func_ptr, {names...}, {}); -} - -template::value>> -ProtocolFunction, std::tuple> make_protocol_function(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) { - return ProtocolFunction, std::tuple>(name, obj, func_ptr, {names...}, {"result"}); -} - - - -template -class EndpointProvider_from_MemberList : public EndpointProvider { -public: - EndpointProvider_from_MemberList(T& member_list) : member_list_(member_list) {} - size_t get_endpoint_count() final { - return T::endpoint_count; - } - void write_json(size_t id, StreamSink* output) final { - return member_list_.write_json(id, output); - } - void register_endpoints(Endpoint** list, size_t id, size_t length) final { - return member_list_.register_endpoints(list, id, length); - } - Endpoint* get_by_name(char * name, size_t length) final { - for (size_t i = 0; i < length; i++) { - if (name[i] == '.') - name[i] = 0; - } - name[length-1] = 0; - return member_list_.get_by_name(name, length); - } - T& member_list_; -}; - -void set_application_endpoints(EndpointProvider* endpoints); - - -// defined in communication.cpp -extern Endpoint* endpoints_[]; -extern size_t n_endpoints_; -extern const size_t max_endpoints_; -extern EndpointProvider* application_endpoints; - -#endif diff --git a/Firmware/fibre/README.md b/Firmware/fibre/README.md index a33c6103..0b5f3848 100644 --- a/Firmware/fibre/README.md +++ b/Firmware/fibre/README.md @@ -74,7 +74,7 @@ Say you want to publish `test_object` so that a remote Fibre node can use it. FIBRE_EXPORTS(TestClass, make_protocol_property("property1", &property1), make_protocol_property("property2", &property2), - make_protocol_function("set_both", obj, &TestClass::set_both, "arg1", "arg2") + make_protocol_function("set_both", *obj, &TestClass::set_both, "arg1", "arg2") ); }; ``` diff --git a/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp b/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp index 7693b5ea..bbb172c0 100644 --- a/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp +++ b/Firmware/fibre/cpp/include/fibre/cpp_utils.hpp @@ -129,7 +129,7 @@ public: #include #define ENABLE_IF(...) \ - typename = std::enable_if_t<__VA_ARGS__> + typename = std::enable_if_t<__VA_ARGS__> template M get_member_type(M T:: *); @@ -141,4 +141,41 @@ template M get_member_type(M T:: *); #define EXPECT_TYPE(T, BaseType) static_assert(std::is_base_of::type>::value || std::is_convertible::type, BaseType>::value, "expected template argument of type " #BaseType) //#define EXPECT_TYPE(T, BaseType) static_assert(, "expected template argument of type " #BaseType) + + + +template +class function_traits { +public: + template + static TRet invoke(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple packed_args, TUnpackedArgs ... args) { + return invoke(obj, func_ptr, packed_args, args..., std::get(packed_args)); + } + + template + static TRet invoke(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple packed_args, TArgs ... args) { + return (obj.*func_ptr)(args...); + } +}; + +/* @brief Invoke a class member function with a variable number of arguments that are supplied as a tuple + +Example usage: + +class MyClass { +public: + int MyFunction(int a, int b) { + return 0; + } +}; + +MyClass my_object; +std::tuple my_args(3, 4); // arguments are supplied as a tuple +int result = invoke_function_with_tuple(my_object, &MyClass::MyFunction, my_args); +*/ +template +TRet invoke_function_with_tuple(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple packed_args) { + return function_traits::template invoke<0>(obj, func_ptr, packed_args); +} + #endif // __CPP_UTILS_HPP diff --git a/Firmware/fibre/cpp/include/fibre/protocol.hpp b/Firmware/fibre/cpp/include/fibre/protocol.hpp index 9750ab82..31ab8334 100644 --- a/Firmware/fibre/cpp/include/fibre/protocol.hpp +++ b/Firmware/fibre/cpp/include/fibre/protocol.hpp @@ -117,6 +117,19 @@ inline size_t write_le(int32_t value, uint8_t* buffer) { return 4; } +template<> +inline size_t write_le(uint64_t value, uint8_t* buffer) { + buffer[0] = (value >> 0) & 0xff; + buffer[1] = (value >> 8) & 0xff; + buffer[2] = (value >> 16) & 0xff; + buffer[3] = (value >> 24) & 0xff; + buffer[4] = (value >> 32) & 0xff; + buffer[5] = (value >> 40) & 0xff; + buffer[6] = (value >> 48) & 0xff; + buffer[7] = (value >> 56) & 0xff; + return 8; +} + template<> inline size_t write_le(float value, uint8_t* buffer) { static_assert(CHAR_BIT * sizeof(float) == 32, "32 bit floating point expected"); @@ -162,6 +175,19 @@ inline size_t read_le(uint32_t* value, const uint8_t* buffer) { return 4; } +template<> +inline size_t read_le(uint64_t* value, const uint8_t* buffer) { + *value = (static_cast(buffer[0]) << 0) | + (static_cast(buffer[1]) << 8) | + (static_cast(buffer[2]) << 16) | + (static_cast(buffer[3]) << 24) | + (static_cast(buffer[4]) << 32) | + (static_cast(buffer[5]) << 40) | + (static_cast(buffer[6]) << 48) | + (static_cast(buffer[7]) << 56); + return 8; +} + template<> inline size_t read_le(float* value, const uint8_t* buffer) { static_assert(CHAR_BIT * sizeof(float) == 32, "32 bit floating point expected"); @@ -186,7 +212,7 @@ public: // @brief Get the maximum packet length (aka maximum transmission unit) // A packet size shall take no action and return an error code if the // caller attempts to send an oversized packet. - virtual size_t get_mtu() = 0; + //virtual size_t get_mtu() = 0; // @brief Processes a packet. // The blocking behavior shall depend on the thread-local deadline_ms variable. @@ -258,7 +284,7 @@ public: { }; - size_t get_mtu() { return SIZE_MAX; } + //size_t get_mtu() { return SIZE_MAX; } int process_packet(const uint8_t *buffer, size_t length); private: @@ -275,7 +301,7 @@ public: int process_bytes(const uint8_t* buffer, size_t length, size_t* processed_bytes) { // Loop to ensure all bytes get sent while (length) { - size_t chunk = length < _packet_sink.get_mtu() ? length : _packet_sink.get_mtu(); + size_t chunk = length; // send chunk as packet if (_packet_sink.process_packet(buffer, chunk)) return -1; @@ -427,6 +453,14 @@ inline constexpr const char* get_default_json_modifier() { return "\"type\":\"float\",\"access\":\"rw\""; } template<> +inline constexpr const char* get_default_json_modifier() { + return "\"type\":\"uint64\",\"access\":\"r\""; +} +template<> +inline constexpr const char* get_default_json_modifier() { + return "\"type\":\"uint64\",\"access\":\"rw\""; +} +template<> inline constexpr const char* get_default_json_modifier() { return "\"type\":\"int32\",\"access\":\"r\""; } @@ -471,9 +505,10 @@ class Endpoint { public: //const char* const name_; virtual void handle(const uint8_t* input, size_t input_length, StreamSink* output) = 0; + virtual bool get_string(char * output, size_t length) { return false; }; + virtual bool set_string(char * buffer, size_t length) { return false; } }; - static inline int write_string(const char* str, StreamSink* output) { return output->process_bytes(reinterpret_cast(str), strlen(str), nullptr); } @@ -492,9 +527,9 @@ public: output_(output) { } - size_t get_mtu() { - return SIZE_MAX; - } + //size_t get_mtu() { + // return SIZE_MAX; + //} int process_packet(const uint8_t* buffer, size_t length); private: PacketSink& output_; @@ -502,6 +537,59 @@ private: }; +/* ToString / FromString functions -------------------------------------------*/ +/* +* These functions are currently not used by Fibre and only here to +* support the ODrive ASCII protocol. +* TODO: find a general way for client code to augment endpoints with custom +* functions +*/ + +template +struct format_traits_t; + +template<> struct format_traits_t { static constexpr const char * fmt = "%f"; }; +template<> struct format_traits_t { static constexpr const char * fmt = "%ld"; }; +template<> struct format_traits_t { static constexpr const char * fmt = "%lu"; }; + +template::fmt> +static bool to_string(const T& value, char * buffer, size_t length, int) { + snprintf(buffer, length, format_traits_t::fmt, value); + return true; +} +template +//__attribute__((__unused__)) +static bool to_string(const bool& value, char * buffer, size_t length, int) { + buffer[0] = value ? '1' : '0'; + buffer[1] = 0; + return true; +} +template +static bool to_string(const T& value, char * buffer, size_t length, ...) { + return false; +} + +template::fmt> +static bool from_string(const char * buffer, size_t length, T* property, int) { + return sscanf(buffer, format_traits_t::fmt, property) == 1; +} +//__attribute__((__unused__)) +template +static bool from_string(const char * buffer, size_t length, bool* property, int) { + int val; + if (sscanf(buffer, "%d", &val) != 1) + return false; + *property = val; + return true; +} +template +static bool from_string(const char * buffer, size_t length, T* property, ...) { + return false; +} + + +/* Object tree ---------------------------------------------------------------*/ + template struct MemberList; @@ -516,6 +604,9 @@ public: void register_endpoints(Endpoint** list, size_t id, size_t length) { // no action } + Endpoint* get_by_name(const char * name, size_t length) { + return nullptr; + } std::tuple<> get_names_as_tuple() const { return std::tuple<>(); } }; @@ -545,6 +636,12 @@ public: subsequent_members_.write_json(id + TMember::endpoint_count, output); } + Endpoint* get_by_name(const char * name, size_t length) { + Endpoint* result = this_member_.get_by_name(name, length); + if (result) return result; + else return subsequent_members_.get_by_name(name, length); + } + void register_endpoints(Endpoint** list, size_t id, size_t length) /*final*/ { this_member_.register_endpoints(list, id, length); subsequent_members_.register_endpoints(list, id + TMember::endpoint_count, length); @@ -576,6 +673,14 @@ public: write_string("]}", output); } + Endpoint* get_by_name(const char * name, size_t length) { + size_t segment_length = strlen(name); + if (!strncmp(name, name_, length)) + return member_list_.get_by_name(name + segment_length + 1, length - segment_length - 1); + else + return nullptr; + } + void register_endpoints(Endpoint** list, size_t id, size_t length) { member_list_.register_endpoints(list, id, length); } @@ -633,7 +738,7 @@ public: // write endpoint ID write_string("\",\"id\":", output); char id_buf[10]; - snprintf(id_buf, sizeof(id_buf), "%zu", id); // TODO: get rid of printf + snprintf(id_buf, sizeof(id_buf), "%u", (unsigned)id); // TODO: get rid of printf write_string(id_buf, output); // write additional JSON data @@ -645,6 +750,24 @@ public: write_string("}", output); } + // special-purpose function - to be moved + Endpoint* get_by_name(const char * name, size_t length) { + if (!strncmp(name, name_, length)) + return this; + else + return nullptr; + } + + // special-purpose function - to be moved + bool get_string(char * buffer, size_t length) final { + return to_string(*property_, buffer, length, 0); + } + + // special-purpose function - to be moved + bool set_string(char * buffer, size_t length) final { + return from_string(buffer, length, property_, 0); + } + void register_endpoints(Endpoint** list, size_t id, size_t length) { if (id < length) list[id] = this; @@ -685,42 +808,6 @@ ProtocolProperty> make_protocol_ro_prope }; - -template -class FunctionTraits { -public: - template - static TRet invoke(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple packed_args, TUnpackedArgs ... args) { - return invoke(obj, func_ptr, packed_args, args..., std::get(packed_args)); - } - - template - static TRet invoke(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple packed_args, TArgs ... args) { - return (obj.*func_ptr)(args...); - } -}; - -/* @brief Invoke a class member function with a variable number of arguments that are supplied as a tuple - -Example usage: - -class MyClass { -public: - int MyFunction(int a, int b) { - return 0; - } -}; - -MyClass my_object; -std::tuple my_args(3, 4); // arguments are supplied as a tuple -int result = invoke_function_with_tuple(my_object, &MyClass::MyFunction, my_args); -*/ -template -TRet invoke_function_with_tuple(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple packed_args) { - return FunctionTraits::template invoke<0>(obj, func_ptr, packed_args); -} - - template struct PropertyListFactory; @@ -744,27 +831,46 @@ struct PropertyListFactory { } }; +/* @brief return_type::type represents the true return type +* of a function returning 0 or more arguments. +* +* For an empty TypeList, the return type is void. For a list with +* one type, the return type is equal to that type. For a list with +* more than one items, the return type is a tuple. +*/ +template +struct return_type; -template -class ProtocolFunction : Endpoint { +template<> +struct return_type<> { typedef void type; }; +template +struct return_type { typedef T type; }; +template +struct return_type { typedef std::tuple type; }; + + +template +class ProtocolFunction; + +template +class ProtocolFunction, std::tuple> : Endpoint { public: - static constexpr size_t endpoint_count = 1 + MemberList...>::endpoint_count; - template - ProtocolFunction(const char * name, TObj* obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) : - name_(name), all_arg_names_{names...}, obj_(obj), func_ptr_(func_ptr), - input_properties_(PropertyListFactory::template make_property_list<0>(all_arg_names_, in_args_)) + // @brief The return type of the function as written by a C++ programmer + using TRet = typename return_type::type; + + static constexpr size_t endpoint_count = 1 + MemberList...>::endpoint_count + MemberList...>::endpoint_count; + + ProtocolFunction(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TInputs...), + std::array input_names, + std::array output_names) : + name_(name), obj_(&obj), func_ptr_(func_ptr), + input_names_{input_names}, output_names_{output_names}, + input_properties_(PropertyListFactory::template make_property_list<0>(input_names_, in_args_)), + output_properties_(PropertyListFactory::template make_property_list<0>(output_names_, out_args_)) { LOG_FIBRE("my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); } - ProtocolFunction(const ProtocolFunction& other) : - name_(other.name_), all_arg_names_(other.all_arg_names_), obj_(other.obj_), func_ptr_(other.func_ptr_), - input_properties_(PropertyListFactory::template make_property_list<0>( - all_arg_names_, in_args_)) - { - LOG_FIBRE("COPIED! my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); - } - void write_json(size_t id, StreamSink* output) { // write name write_string("{\"name\":\"", output); @@ -773,19 +879,42 @@ public: // write endpoint ID write_string("\",\"id\":", output); char id_buf[10]; - snprintf(id_buf, sizeof(id_buf), "%zu", id); // TODO: get rid of printf + snprintf(id_buf, sizeof(id_buf), "%u", (unsigned)id); // TODO: get rid of printf write_string(id_buf, output); // write arguments - write_string(",\"type\":\"function\",\"arguments\":[", output); + write_string(",\"type\":\"function\",\"inputs\":[", output); input_properties_.write_json(id + 1, output), + write_string("],\"outputs\":[", output); + output_properties_.write_json(id + 1 + decltype(input_properties_)::endpoint_count, output), write_string("]}", output); } + // special-purpose function - to be moved + Endpoint* get_by_name(const char * name, size_t length) { + return nullptr; // can't address functions by name + } + void register_endpoints(Endpoint** list, size_t id, size_t length) { if (id < length) list[id] = this; input_properties_.register_endpoints(list, id + 1, length); + output_properties_.register_endpoints(list, id + 1 + decltype(input_properties_)::endpoint_count, length); + } + + template std::enable_if_t + handle_ex() { + invoke_function_with_tuple(*obj_, func_ptr_, in_args_); + } + + template std::enable_if_t + handle_ex() { + std::get<0>(out_args_) = invoke_function_with_tuple(*obj_, func_ptr_, in_args_); + } + + template std::enable_if_t= 2> + handle_ex() { + out_args_ = invoke_function_with_tuple(*obj_, func_ptr_, in_args_); } void handle(const uint8_t* input, size_t input_length, StreamSink* output) { @@ -794,20 +923,30 @@ public: (void) output; LOG_FIBRE("tuple still at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); LOG_FIBRE("invoke function using %d and %.3f\r\n", std::get<0>(in_args_), std::get<1>(in_args_)); - invoke_function_with_tuple(*obj_, func_ptr_, in_args_); + handle_ex(); } const char * name_; - std::array all_arg_names_; // TODO: remove TObj* obj_; - TRet(TObj::*func_ptr_)(TArgs...); - std::tuple in_args_; - MemberList...> input_properties_; + TRet(TObj::*func_ptr_)(TInputs...); + std::array input_names_; // TODO: remove + std::array output_names_; // TODO: remove + std::tuple in_args_; + std::tuple out_args_; + MemberList...> input_properties_; + MemberList...> output_properties_; }; -template -ProtocolFunction make_protocol_function(const char * name, TObj* obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) { - return ProtocolFunction(name, obj, func_ptr, names...); +template> +ProtocolFunction, std::tuple<>> make_protocol_function(const char * name, TObj& obj, void(TObj::*func_ptr)(TArgs...), TNames ... names) { + return ProtocolFunction, std::tuple<>>(name, obj, func_ptr, {names...}, {}); +} + +template::value>> +ProtocolFunction, std::tuple> make_protocol_function(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) { + return ProtocolFunction, std::tuple>(name, obj, func_ptr, {names...}, {"result"}); } @@ -825,24 +964,41 @@ ProtocolFunction make_protocol_function(const char * name, -// TODO: this is ugly => remove -class JSONWriter { + +class EndpointProvider { public: + virtual size_t get_endpoint_count() = 0; virtual void write_json(size_t id, StreamSink* output) = 0; + virtual Endpoint* get_by_name(char * name, size_t length) = 0; + virtual void register_endpoints(Endpoint** list, size_t id, size_t length) = 0; }; -// TODO: this is ugly => remove template -class JSONWriter_from_MemberList : public JSONWriter { +class EndpointProvider_from_MemberList : public EndpointProvider { public: - JSONWriter_from_MemberList(T* impl) : impl_(impl) {} - void write_json(size_t id, StreamSink* output) { - impl_->write_json(id, output); + EndpointProvider_from_MemberList(T& member_list) : member_list_(member_list) {} + size_t get_endpoint_count() final { + return T::endpoint_count; } - T* impl_; + void write_json(size_t id, StreamSink* output) final { + return member_list_.write_json(id, output); + } + void register_endpoints(Endpoint** list, size_t id, size_t length) final { + return member_list_.register_endpoints(list, id, length); + } + Endpoint* get_by_name(char * name, size_t length) final { + for (size_t i = 0; i < length; i++) { + if (name[i] == '.') + name[i] = 0; + } + name[length-1] = 0; + return member_list_.get_by_name(name, length); + } + T& member_list_; }; + class JSONDescriptorEndpoint : Endpoint { public: static constexpr size_t endpoint_count = 1; @@ -856,7 +1012,7 @@ extern Endpoint** endpoint_list_; extern size_t n_endpoints_; extern uint16_t json_crc_; extern JSONDescriptorEndpoint json_file_endpoint_; -extern JSONWriter* application_json_writer_; +extern EndpointProvider* application_endpoints_; // @brief Registers the specified application object list using the provided endpoint table. // This function should only be called once during the lifetime of the application. TODO: fix this. @@ -865,6 +1021,7 @@ template int fibre_publish(T& application_objects) { static constexpr size_t endpoint_list_size = 1 + T::endpoint_count; static Endpoint* endpoint_list[endpoint_list_size]; + static auto endpoint_provider = EndpointProvider_from_MemberList(application_objects); json_file_endpoint_.register_endpoints(endpoint_list, 0, endpoint_list_size); application_objects.register_endpoints(endpoint_list, 1, endpoint_list_size); @@ -872,8 +1029,7 @@ int fibre_publish(T& application_objects) { // Update the global endpoint table endpoint_list_ = endpoint_list; n_endpoints_ = endpoint_list_size; - // TODO: fix use of dynamic memory - application_json_writer_ = new JSONWriter_from_MemberList(&application_objects); + application_endpoints_ = &endpoint_provider; // Calculate the CRC16 of the JSON file. // The init value is the protocol version. diff --git a/Firmware/fibre/cpp/protocol.cpp b/Firmware/fibre/cpp/protocol.cpp index a0c7f814..9b22f163 100644 --- a/Firmware/fibre/cpp/protocol.cpp +++ b/Firmware/fibre/cpp/protocol.cpp @@ -17,7 +17,7 @@ Endpoint** endpoint_list_ = nullptr; // initialized by calling fibre_publish size_t n_endpoints_ = 0; // initialized by calling fibre_publish uint16_t json_crc_; // initialized by calling fibre_publish JSONDescriptorEndpoint json_file_endpoint_ = JSONDescriptorEndpoint(); -JSONWriter* application_json_writer_; +EndpointProvider* application_endpoints_; /* Private constant data -----------------------------------------------------*/ /* Private variables ---------------------------------------------------------*/ @@ -122,7 +122,7 @@ void JSONDescriptorEndpoint::write_json(size_t id, StreamSink* output) { // write endpoint ID write_string("\"id\":", output); char id_buf[10]; - snprintf(id_buf, sizeof(id_buf), "%zu", id); // TODO: get rid of printf + snprintf(id_buf, sizeof(id_buf), "%u", (unsigned)id); // TODO: get rid of printf write_string(id_buf, output); write_string(",\"type\":\"json\",\"access\":\"r\"}", output); @@ -147,7 +147,7 @@ void JSONDescriptorEndpoint::handle(const uint8_t* input, size_t input_length, S json_file_endpoint_.write_json(id, &output_with_offset); id += decltype(json_file_endpoint_)::endpoint_count; write_string(",", &output_with_offset); - application_json_writer_->write_json(id, &output_with_offset); + application_endpoints_->write_json(id, &output_with_offset); write_string("]", &output_with_offset); } diff --git a/Firmware/fibre/test/test_server.cpp b/Firmware/fibre/test/test_server.cpp index ffd1d50b..9aa849b8 100644 --- a/Firmware/fibre/test/test_server.cpp +++ b/Firmware/fibre/test/test_server.cpp @@ -23,7 +23,7 @@ public: FIBRE_EXPORTS(TestClass, make_protocol_property("property1", &property1), make_protocol_property("property2", &property2), - make_protocol_function("set_both", obj, &TestClass::set_both, "arg1", "arg2") + make_protocol_function("set_both", *obj, &TestClass::set_both, "arg1", "arg2") ); };