mirror of
https://github.com/odriverobotics/ODrive.git
synced 2026-08-18 18:20:15 +08:00
apply ODrive native protocol updates to fibre and switch to fibre
This commit is contained in:
@@ -12,11 +12,12 @@
|
||||
#include <stm32f405xx.h>
|
||||
|
||||
#include "nvm.h"
|
||||
#include <communication/crc.hpp>
|
||||
#include <fibre/crc.hpp>
|
||||
|
||||
|
||||
/* Private defines -----------------------------------------------------------*/
|
||||
#define CONFIG_CRC16_INIT 0xabcd
|
||||
#define CONFIG_CRC16_POLYNOMIAL 0x3d65
|
||||
|
||||
/* Private macros ------------------------------------------------------------*/
|
||||
/* Private typedef -----------------------------------------------------------*/
|
||||
@@ -76,7 +77,7 @@ struct Config<T, Ts...> {
|
||||
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<CONFIG_CRC16_POLYNOMIAL>(previous_crc16, (uint8_t *)val0, size);
|
||||
if (Config<Ts...>::load_config(offset + size, crc16, vals...))
|
||||
return -1;
|
||||
return 0;
|
||||
@@ -94,7 +95,7 @@ struct Config<T, Ts...> {
|
||||
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<CONFIG_CRC16_POLYNOMIAL>(*crc16, (uint8_t *)val0, size);
|
||||
if (Config<Ts...>::store_config(offset + size, crc16, vals...))
|
||||
return -1;
|
||||
return 0;
|
||||
|
||||
@@ -88,7 +88,7 @@ inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast<ENUMTYPE>(~static_c
|
||||
|
||||
|
||||
// ODrive specific includes
|
||||
#include <communication/protocol.hpp>
|
||||
#include <fibre/protocol.hpp>
|
||||
#include <utils.h>
|
||||
#include <low_level.h>
|
||||
#include <encoder.hpp>
|
||||
|
||||
@@ -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',
|
||||
'.'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
|
||||
#include "odrive_main.h"
|
||||
#include "communication.h"
|
||||
#include "ascii_protocol.h"
|
||||
#include "ascii_protocol.hpp"
|
||||
#include <utils.h>
|
||||
#include <fibre/cpp_utils.hpp>
|
||||
|
||||
/* Private macros ------------------------------------------------------------*/
|
||||
/* Private typedef -----------------------------------------------------------*/
|
||||
@@ -31,15 +32,15 @@ template<typename ... TArgs>
|
||||
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<TArgs>(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 {
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
#ifndef __ASCII_PROTOCOL_H
|
||||
#define __ASCII_PROTOCOL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Includes ------------------------------------------------------------------*/
|
||||
|
||||
#include "protocol.hpp"
|
||||
#include <fibre/protocol.hpp>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
@@ -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 */
|
||||
@@ -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_type>(*tree_ptr);
|
||||
set_application_endpoints(&endpoint_provider);
|
||||
fibre_publish(*tree_ptr);
|
||||
|
||||
serve_on_uart();
|
||||
serve_on_usb();
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include "crc.hpp"
|
||||
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
#ifndef __CRC_HPP
|
||||
#define __CRC_HPP
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
// 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<typename T, unsigned POLYNOMIAL>
|
||||
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<typename T, unsigned POLYNOMIAL>
|
||||
static T calc_crc(T remainder, const uint8_t* buffer, size_t length) {
|
||||
while (length--)
|
||||
remainder = calc_crc<T, POLYNOMIAL>(remainder, *(buffer++));
|
||||
return remainder;
|
||||
}
|
||||
|
||||
template<unsigned POLYNOMIAL = CRC8_DEFAULT>
|
||||
static uint8_t calc_crc8(uint8_t remainder, uint8_t value) {
|
||||
return calc_crc<uint8_t, POLYNOMIAL>(remainder, value);
|
||||
}
|
||||
|
||||
template<unsigned POLYNOMIAL = CRC16_DEFAULT>
|
||||
static uint16_t calc_crc16(uint16_t remainder, uint8_t value) {
|
||||
return calc_crc<uint16_t, POLYNOMIAL>(remainder, value);
|
||||
}
|
||||
|
||||
template<unsigned POLYNOMIAL = CRC8_DEFAULT>
|
||||
static uint8_t calc_crc8(uint8_t remainder, const uint8_t* buffer, size_t length) {
|
||||
return calc_crc<uint8_t, POLYNOMIAL>(remainder, buffer, length);
|
||||
}
|
||||
|
||||
template<unsigned POLYNOMIAL = CRC16_DEFAULT>
|
||||
static uint16_t calc_crc16(uint16_t remainder, const uint8_t* buffer, size_t length) {
|
||||
return calc_crc<uint16_t, POLYNOMIAL>(remainder, buffer, length);
|
||||
}
|
||||
|
||||
#endif /* __CRC_HPP */
|
||||
@@ -1,11 +1,11 @@
|
||||
|
||||
#include "interface_uart.h"
|
||||
#include "protocol.hpp"
|
||||
|
||||
#include "ascii_protocol.h"
|
||||
#include "ascii_protocol.hpp"
|
||||
|
||||
#include <MotorControl/utils.h>
|
||||
|
||||
#include <fibre/protocol.hpp>
|
||||
#include <usart.h>
|
||||
#include <cmsis_os.h>
|
||||
#include <freertos_vars.h>
|
||||
@@ -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;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
|
||||
#include "interface_usb.h"
|
||||
#include "protocol.hpp"
|
||||
|
||||
#include <MotorControl/utils.h>
|
||||
|
||||
#include <fibre/protocol.hpp>
|
||||
#include <usbd_cdc.h>
|
||||
#include <usbd_cdc_if.h>
|
||||
#include <usb_device.h>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,239 +0,0 @@
|
||||
|
||||
/* Includes ------------------------------------------------------------------*/
|
||||
|
||||
//#include "low_level.h"
|
||||
#include "protocol.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* 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<uint8_t>(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<uint32_t>(&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<uint16_t>(&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<uint16_t>(&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<uint16_t>(&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<uint16_t>(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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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")
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
@@ -129,7 +129,7 @@ public:
|
||||
|
||||
#include <type_traits>
|
||||
#define ENABLE_IF(...) \
|
||||
typename = std::enable_if_t<__VA_ARGS__>
|
||||
typename = std::enable_if_t<__VA_ARGS__>
|
||||
|
||||
|
||||
template <class T, class M> M get_member_type(M T:: *);
|
||||
@@ -141,4 +141,41 @@ template <class T, class M> M get_member_type(M T:: *);
|
||||
#define EXPECT_TYPE(T, BaseType) static_assert(std::is_base_of<BaseType, typename std::decay<T>::type>::value || std::is_convertible<typename std::decay<T>::type, BaseType>::value, "expected template argument of type " #BaseType)
|
||||
//#define EXPECT_TYPE(T, BaseType) static_assert(, "expected template argument of type " #BaseType)
|
||||
|
||||
|
||||
|
||||
|
||||
template<typename TObj, typename TRet, typename ... TArgs>
|
||||
class function_traits {
|
||||
public:
|
||||
template<unsigned IUnpacked, typename ... TUnpackedArgs, ENABLE_IF(IUnpacked != sizeof...(TArgs))>
|
||||
static TRet invoke(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple<TArgs...> packed_args, TUnpackedArgs ... args) {
|
||||
return invoke<IUnpacked+1>(obj, func_ptr, packed_args, args..., std::get<IUnpacked>(packed_args));
|
||||
}
|
||||
|
||||
template<unsigned IUnpacked>
|
||||
static TRet invoke(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple<TArgs...> 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<int, int> my_args(3, 4); // arguments are supplied as a tuple
|
||||
int result = invoke_function_with_tuple(my_object, &MyClass::MyFunction, my_args);
|
||||
*/
|
||||
template<typename TObj, typename TRet, typename ... TArgs>
|
||||
TRet invoke_function_with_tuple(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple<TArgs...> packed_args) {
|
||||
return function_traits<TObj, TRet, TArgs...>::template invoke<0>(obj, func_ptr, packed_args);
|
||||
}
|
||||
|
||||
#endif // __CPP_UTILS_HPP
|
||||
|
||||
@@ -117,6 +117,19 @@ inline size_t write_le<int32_t>(int32_t value, uint8_t* buffer) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline size_t write_le<uint64_t>(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>(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>(uint32_t* value, const uint8_t* buffer) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline size_t read_le<uint64_t>(uint64_t* value, const uint8_t* buffer) {
|
||||
*value = (static_cast<uint64_t>(buffer[0]) << 0) |
|
||||
(static_cast<uint64_t>(buffer[1]) << 8) |
|
||||
(static_cast<uint64_t>(buffer[2]) << 16) |
|
||||
(static_cast<uint64_t>(buffer[3]) << 24) |
|
||||
(static_cast<uint64_t>(buffer[4]) << 32) |
|
||||
(static_cast<uint64_t>(buffer[5]) << 40) |
|
||||
(static_cast<uint64_t>(buffer[6]) << 48) |
|
||||
(static_cast<uint64_t>(buffer[7]) << 56);
|
||||
return 8;
|
||||
}
|
||||
|
||||
template<>
|
||||
inline size_t read_le<float>(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<float>() {
|
||||
return "\"type\":\"float\",\"access\":\"rw\"";
|
||||
}
|
||||
template<>
|
||||
inline constexpr const char* get_default_json_modifier<const uint64_t>() {
|
||||
return "\"type\":\"uint64\",\"access\":\"r\"";
|
||||
}
|
||||
template<>
|
||||
inline constexpr const char* get_default_json_modifier<uint64_t>() {
|
||||
return "\"type\":\"uint64\",\"access\":\"rw\"";
|
||||
}
|
||||
template<>
|
||||
inline constexpr const char* get_default_json_modifier<const int32_t>() {
|
||||
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<const uint8_t*>(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<typename T>
|
||||
struct format_traits_t;
|
||||
|
||||
template<> struct format_traits_t<float> { static constexpr const char * fmt = "%f"; };
|
||||
template<> struct format_traits_t<int32_t> { static constexpr const char * fmt = "%ld"; };
|
||||
template<> struct format_traits_t<uint32_t> { static constexpr const char * fmt = "%lu"; };
|
||||
|
||||
template<typename T, typename = typename format_traits_t<T>::fmt>
|
||||
static bool to_string(const T& value, char * buffer, size_t length, int) {
|
||||
snprintf(buffer, length, format_traits_t<T>::fmt, value);
|
||||
return true;
|
||||
}
|
||||
template<typename T>
|
||||
//__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<typename T>
|
||||
static bool to_string(const T& value, char * buffer, size_t length, ...) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename T, typename = typename format_traits_t<T>::fmt>
|
||||
static bool from_string(const char * buffer, size_t length, T* property, int) {
|
||||
return sscanf(buffer, format_traits_t<T>::fmt, property) == 1;
|
||||
}
|
||||
//__attribute__((__unused__))
|
||||
template<typename T>
|
||||
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<typename T>
|
||||
static bool from_string(const char * buffer, size_t length, T* property, ...) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/* Object tree ---------------------------------------------------------------*/
|
||||
|
||||
template<typename ... TMembers>
|
||||
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<const std::underlying_type_t<TProperty>> make_protocol_ro_prope
|
||||
};
|
||||
|
||||
|
||||
|
||||
template<typename TObj, typename TRet, typename ... TArgs>
|
||||
class FunctionTraits {
|
||||
public:
|
||||
template<unsigned IUnpacked, typename ... TUnpackedArgs, ENABLE_IF(IUnpacked != sizeof...(TArgs))>
|
||||
static TRet invoke(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple<TArgs...> packed_args, TUnpackedArgs ... args) {
|
||||
return invoke<IUnpacked+1>(obj, func_ptr, packed_args, args..., std::get<IUnpacked>(packed_args));
|
||||
}
|
||||
|
||||
template<unsigned IUnpacked>
|
||||
static TRet invoke(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple<TArgs...> 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<int, int> my_args(3, 4); // arguments are supplied as a tuple
|
||||
int result = invoke_function_with_tuple(my_object, &MyClass::MyFunction, my_args);
|
||||
*/
|
||||
template<typename TObj, typename TRet, typename ... TArgs>
|
||||
TRet invoke_function_with_tuple(TObj& obj, TRet(TObj::*func_ptr)(TArgs...), std::tuple<TArgs...> packed_args) {
|
||||
return FunctionTraits<TObj, TRet, TArgs...>::template invoke<0>(obj, func_ptr, packed_args);
|
||||
}
|
||||
|
||||
|
||||
template<typename ... TArgs>
|
||||
struct PropertyListFactory;
|
||||
|
||||
@@ -744,27 +831,46 @@ struct PropertyListFactory<TProperty, TProperties...> {
|
||||
}
|
||||
};
|
||||
|
||||
/* @brief return_type<TypeList>::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<typename ... Types>
|
||||
struct return_type;
|
||||
|
||||
template<typename TObj, typename TRet, typename ... TArgs>
|
||||
class ProtocolFunction : Endpoint {
|
||||
template<>
|
||||
struct return_type<> { typedef void type; };
|
||||
template<typename T>
|
||||
struct return_type<T> { typedef T type; };
|
||||
template<typename T, typename ... Ts>
|
||||
struct return_type<T, Ts...> { typedef std::tuple<T, Ts...> type; };
|
||||
|
||||
|
||||
template<typename TObj, typename ... TInputsAndOutputs>
|
||||
class ProtocolFunction;
|
||||
|
||||
template<typename TObj, typename ... TInputs, typename ... TOutputs>
|
||||
class ProtocolFunction<TObj, std::tuple<TInputs...>, std::tuple<TOutputs...>> : Endpoint {
|
||||
public:
|
||||
static constexpr size_t endpoint_count = 1 + MemberList<ProtocolProperty<TArgs>...>::endpoint_count;
|
||||
template<typename ... TNames>
|
||||
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<TArgs...>::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<TOutputs...>::type;
|
||||
|
||||
static constexpr size_t endpoint_count = 1 + MemberList<ProtocolProperty<TInputs>...>::endpoint_count + MemberList<ProtocolProperty<TOutputs>...>::endpoint_count;
|
||||
|
||||
ProtocolFunction(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TInputs...),
|
||||
std::array<const char *, sizeof...(TInputs)> input_names,
|
||||
std::array<const char *, sizeof...(TOutputs)> output_names) :
|
||||
name_(name), obj_(&obj), func_ptr_(func_ptr),
|
||||
input_names_{input_names}, output_names_{output_names},
|
||||
input_properties_(PropertyListFactory<TInputs...>::template make_property_list<0>(input_names_, in_args_)),
|
||||
output_properties_(PropertyListFactory<TOutputs...>::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<TArgs...>::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<typename> std::enable_if_t<sizeof...(TOutputs) == 0>
|
||||
handle_ex() {
|
||||
invoke_function_with_tuple(*obj_, func_ptr_, in_args_);
|
||||
}
|
||||
|
||||
template<typename> std::enable_if_t<sizeof...(TOutputs) == 1>
|
||||
handle_ex() {
|
||||
std::get<0>(out_args_) = invoke_function_with_tuple(*obj_, func_ptr_, in_args_);
|
||||
}
|
||||
|
||||
template<typename> std::enable_if_t<sizeof...(TOutputs) >= 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<void>();
|
||||
}
|
||||
|
||||
const char * name_;
|
||||
std::array<const char *, sizeof...(TArgs)> all_arg_names_; // TODO: remove
|
||||
TObj* obj_;
|
||||
TRet(TObj::*func_ptr_)(TArgs...);
|
||||
std::tuple<TArgs...> in_args_;
|
||||
MemberList<ProtocolProperty<TArgs>...> input_properties_;
|
||||
TRet(TObj::*func_ptr_)(TInputs...);
|
||||
std::array<const char *, sizeof...(TInputs)> input_names_; // TODO: remove
|
||||
std::array<const char *, sizeof...(TOutputs)> output_names_; // TODO: remove
|
||||
std::tuple<TInputs...> in_args_;
|
||||
std::tuple<TOutputs...> out_args_;
|
||||
MemberList<ProtocolProperty<TInputs>...> input_properties_;
|
||||
MemberList<ProtocolProperty<TOutputs>...> output_properties_;
|
||||
};
|
||||
|
||||
template<typename TObj, typename TRet, typename ... TArgs, typename ... TNames, ENABLE_IF(sizeof...(TArgs) == sizeof...(TNames))>
|
||||
ProtocolFunction<TObj, TRet, TArgs...> make_protocol_function(const char * name, TObj* obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) {
|
||||
return ProtocolFunction<TObj, TRet, TArgs...>(name, obj, func_ptr, names...);
|
||||
template<typename TObj, typename ... TArgs, typename ... TNames,
|
||||
typename = std::enable_if_t<sizeof...(TArgs) == sizeof...(TNames)>>
|
||||
ProtocolFunction<TObj, std::tuple<TArgs...>, std::tuple<>> make_protocol_function(const char * name, TObj& obj, void(TObj::*func_ptr)(TArgs...), TNames ... names) {
|
||||
return ProtocolFunction<TObj, std::tuple<TArgs...>, std::tuple<>>(name, obj, func_ptr, {names...}, {});
|
||||
}
|
||||
|
||||
template<typename TObj, typename TRet, typename ... TArgs, typename ... TNames,
|
||||
typename = std::enable_if_t<sizeof...(TArgs) == sizeof...(TNames) && !std::is_void<TRet>::value>>
|
||||
ProtocolFunction<TObj, std::tuple<TArgs...>, std::tuple<TRet>> make_protocol_function(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) {
|
||||
return ProtocolFunction<TObj, std::tuple<TArgs...>, std::tuple<TRet>>(name, obj, func_ptr, {names...}, {"result"});
|
||||
}
|
||||
|
||||
|
||||
@@ -825,24 +964,41 @@ ProtocolFunction<TObj, TRet, TArgs...> 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<typename T>
|
||||
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<typename T>
|
||||
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<T>(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<T>(&application_objects);
|
||||
application_endpoints_ = &endpoint_provider;
|
||||
|
||||
// Calculate the CRC16 of the JSON file.
|
||||
// The init value is the protocol version.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user