apply ODrive native protocol updates to fibre and switch to fibre

This commit is contained in:
Samuel Sadok
2018-05-13 18:53:44 -07:00
parent 24b319259b
commit 4bf7e987a5
17 changed files with 308 additions and 1355 deletions
+4 -3
View File
@@ -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;
+1 -1
View File
@@ -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>
+2 -1
View File
@@ -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',
'.'
}
}
+7 -6
View File
@@ -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 */
+1 -8
View File
@@ -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();
-1
View File
@@ -8,7 +8,6 @@
#include <functional>
#include <limits>
#include "crc.hpp"
extern "C" {
#endif
-66
View File
@@ -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 */
+9 -7
View File
@@ -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;
+4 -2
View File
@@ -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;
}
-239
View File
@@ -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
+1 -1
View File
@@ -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")
);
};
```
+38 -1
View File
@@ -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
+236 -80
View File
@@ -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.
+3 -3
View File
@@ -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);
}
+1 -1
View File
@@ -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")
);
};