Implementing caller side¶
On the caller side, requesting a remote device to invoke a function is pretty much like a regular function invocation. The types of the arguments are checked at compile-time. The type of the return value is also available at compile-time.
What Unpadded does for you is generating the packet and sending it byte by byte through a user-provided callback. What remains to be handled is the implementation of the aforesaid callback.
The following sections will assume these functions to be defined by the caller device:
void write_byte_to_slave(upd::byte_t);
upd::byte_t read_byte_from_slave();
Basics : sending request with keys¶
Preparing a packet for a request is done by using keys from the corresponding keyring. Let say we defined a keyring
object holding the function f
of signature void(std::uint8_t, std::uint16_t)
.
// We create a key to `f`.
auto key = keyring.get<f>();
// Then we generate the packet and write every byte of it
// to the slave device. On the remote device, ``f`` will be
// invoked on the arguments ``16`` and ``64``.
key(16, 64).write_to(write_byte_to_slave);
// This line does the same thing, but with another syntax.
key(16, 64) >> write_byte_to_slave;
If argument types does not match, the compiler will tell you.
std::uint8_t array[] = {0x00, 0x11, 0x22};
auto key = keyring.get<f>();
// What are you doing ?
// vvvvv
key(16, array).write_all(write_byte_to_master);
Once sent, the packet must be received by a dispatcher.
Example¶
In the follwing example, we are requesting the callee device to spiral faster and faster.
#pragma once
#include <cstdint>
#include <upd/format.hpp>
#include <upd/keyring.hpp>
#include <upd/typelist.hpp>
void set_forward_speed(std::uint32_t);
void set_left_steering_speed(std::uint32_t);
void set_right_steering_speed(std::uint32_t);
constexpr upd::keyring motion_keyring{upd::flist<set_forward_speed, set_left_steering_speed, set_right_steering_speed>,
upd::little_endian,
upd::twos_complement};
#pragma once
#include <cstdint>
#include <upd/format.hpp>
#include <upd/keyring.hpp>
#include <upd/typelist.hpp>
void set_forward_speed(std::uint32_t);
void set_left_steering_speed(std::uint32_t);
void set_right_steering_speed(std::uint32_t);
constexpr upd::keyring motion_keyring{upd::flist<set_forward_speed, set_left_steering_speed, set_right_steering_speed>,
upd::little_endian,
upd::twos_complement};
Handling the callee response¶
You are free to ignore the result of an invocation request if you don’t need it. Unpadded won’t handle the response from the callee automatically. If you need to, there is two different way to proceed. Either you send a request then wait until the callee device is done (blocking mode) or you hook a callback to be invoked when receiving a response from the callee device (non-blocking mode).
Blocking mode¶
In blocking mode, keys must be used to extract the result of the invocation request out of the packet sent back by the callee device.
// We assume that a `keyring` object has been defined and
// holds a function `f` of signature `std::uint8_t(std::uint16_t)`.
auto key = keyring.get<f>();
// First we send a request to the slave device
key(64).write_to(write_byte_to_slave);
// Let's wait a little for the slave device to completely
//fulfill the request.
// Then we receive its response
// `x` type is `std::uint8_t`
auto x = key.read_from(read_byte_from_slave);
On bare-metal implementations, the blocking mode is not really useful. If you are using an RTOS however, synchronization primitives can be used in order to wait for the response to be received without blocking the caller device.
Non-blocking mode¶
If you cannot afford using the blocking mode (for example, because you cannot use an RTOS), Unpadded provides you with a portable way to hook actions to be executed on reception of a reponse from the callee device.
In non-blocking mode, the action
class must be used to store a callback for later.
// We assume that a `keyring` object has been defined and
// holds a function `f` of signature `std::uint8_t(std::uint16_t)`
static upd::action action;
void send_request() {
auto key = keyring.get<f>();
// We send the request...
key(64).write_to(write_byte_to_slave);
// ...and then, we save our callback for later
action = key.with_hook(my_callback);
}
void process_response() {
// Our callback is called !
action(read_byte_from_slave);
}
int main() {
send_request();
// Meanwhile, we do other stuff...
process_response();
return 0;
}
action
is non-templated, so it is suitable for storage. The hooked callback must be invocable on whatever value the remotely called function returns. In case of a multimaster architecture (i.e. if both devices can initiate a request), the buffered_dispatcher::reply()
function can come in handy.
API References¶
key
¶
-
template<typename Index_T, Index_T Index, typename F, endianess Endianess, signed_mode Signed_Mode>
class key¶ Packet generator for invoking an action on a callee device.
Keys will generate packets whose structure is the following:
index of the action (value:
Index
; size:sizeof Index
);payload (size:
sizeof(Args) + ...
);
The index is used by the callee to determine which action is addressed. The content of the payload is choosen by the user. In the following example :
int function1(uint8_t, uint16_t); int function2(uint16_t, uint32_t); int function3(uint32_t, uint64_t); constexpr keyring keyring{flist<function1, function2, function3>}; int main() { auto key = keyring.get<function2>(); key(7, 21).write_to(/*an output invocable*/); }
The packet sent will have the following content:
1 byte of index (value: 1)
2 bytes for the first argument of
function2
(type:uint16_t
; value: 7)4 bytes for the second argument of
function2
(type:uint32_t
; value: 21)
When the packet has been processed by the callee device and the resulting value is sent back to the caller device, keys can be used to extract this value from the callee response. The syntax to achieve that is
auto x = key.read_from(/*a byte getter*/)
. However, key instances being templated, it is hard to store them for later. If blocking the program until the callee device response is not a viable option, it is possible to save a callback to be executed once the callee device has finished using the action or no_storage_action classes.- Template Parameters:
Index – Index of the action in the keyring
F – Signature of the callback associated with the key
Endianess, Signed_Mode – Serialization parameters
Public Types
-
using signature_t = R(Args...)¶
Signature of the invocable associated with this key.
-
using return_t = detail::remove_cv_ref_t<R>¶
Return type of read_from() (i.e. the return type of the callback without its reference and cv-qualifier)
-
using tuple_t = tuple<Endianess, Signed_Mode, detail::remove_cv_ref_t<Args>...>¶
Type of the tuple which can be invoked on this key (e.g.
t.invoke(key)
)
Public Functions
-
auto operator()(const Args&... args) const¶
Generate a packet ready to be sent.
This allows the following syntax :
key(x1, x2, x3, ...).write_to(dest)
(withdest
being a byte putter).dest
is invoked on every byte representing the data passed as parameter, in the action they appear in the packet.- Parameters:
args... – Values to insert in the payload
- Returns:
a temporary object allowing the syntax mentioned above
-
template<typename Src>
inline return_t read_from(Src &&src) const¶ Unserialize a value from a packet sent by a callee device in response to a packet generated by this key.
The following member functions are also defined through CRTP.
All these functions work the same way asR operator<<(Src &&); R operator<<(Src &&) const; R read_from(It); R read_from(It) const; R operator<<(It); R operator<<(It) const;
read_from(Src &&)
.- Template Parameters:
Src – Byte getter type
It – Input iterator type
- Parameters:
src – Byte getter
- Returns:
the unserialized value
-
template<typename F>
inline action with_hook(F &&ftor) const¶ Plan an action to perform when a packet resulting from the execution of the action is received.
- Parameters:
ftor – Callback which will carry out the action
- Returns:
an action holding the provided hook
-
template<typename F, F Ftor>
inline no_storage_action with_hook(unevaluated<F, Ftor>) const¶ Plan an action to perform when a packet resulting from the execution of the action is received.
- Template Parameters:
Ftor – Callback which will carry out the action
- Returns:
an action holding the provided hook
-
template<auto &Ftor>
inline auto with_hook() const¶ Plan an action to perform when a packet resulting from the execution of the action is received.
- Template Parameters:
Ftor – Callback which will carry out the action
- Returns:
an action holding the provided hook
Public Static Attributes
-
static constexpr auto signed_mode = Signed_Mode¶
Equals the
Signed_Mode
template parameter.
action
¶
-
class action : public upd::detail::immediate_process<action, void>¶
Wrapper around a callback which serialize / unserialize parameters and return values.
Given a byte sequence generated by a key instance, an action instance (whose underlying callback has the same signature as the aforesaid key instance) is able to unserialize the parameters from that byte sequence, invoke the underlying callback and serialize the return value as a byte sequence (which can later be unserialized by the same key instance to obtain the return value).
Public Functions
-
template<endianess Endianess, signed_mode Signed_Mode, typename F>
inline explicit action(F &&ftor, endianess_h<Endianess>, signed_mode_h<Signed_Mode>)¶ Wrap a copy of a provided callback.
- Template Parameters:
Endianess, Signed_Mode – Serialization parameters
- Parameters:
ftor – Callback to be wrapped
-
template<typename Src, typename Dest>
inline void operator()(Src &&src, Dest &&dest) const¶ Invoke the managed callback.
The parameters are unserialized from the input byte stream
src
. After the callback invocation, the result is serialized intodest
.The following member functions are also defined through CRTP.
All these functions work the same way asauto operator()(Input &&, Output &&); auto operator()(Input &&, Output &&) const;
operator()(Src &&, Dest &&)
.- Template Parameters:
Input – Input byte stream type of any kind
Output – Output byte stream type of any kind
- Parameters:
src – Byte getter
dest – Byte putter
-
template<typename Input>
inline void operator()(Input &&input) const¶ Invoke the managed callback.
- Parameters:
input – Input byte stream
-
inline std::size_t input_size() const¶
Get the size in bytes of the payload needed to invoke the wrapped callback.
- Returns:
The size of the payload in bytes
-
inline std::size_t output_size() const¶
Get the size in bytes of the payload representing the return value of the wrapped callback.
- Returns:
The size of the payload in bytes
-
template<endianess Endianess, signed_mode Signed_Mode, typename F>
no_storage_action
¶
-
class no_storage_action : public upd::detail::immediate_process<no_storage_action, void>¶
Action which does not manage storage for its underlying callback.
no_storage_action instances must be given a free function or a callback with static storage duration, which makes them less permissive than action instances. On the other hand, they do not rely on dynamic allocation, which can be useful for embedded software.
Public Functions
-
template<typename F, F Ftor, endianess Endianess, signed_mode Signed_Mode>
inline explicit no_storage_action(unevaluated<F, Ftor>, endianess_h<Endianess>, signed_mode_h<Signed_Mode>)¶ Create an action holding the provided callback.
- Template Parameters:
Ftor – Free function or callback with static storage duration
Endianess, Signed_Mode – Serialization parameters
-
template<typename Src, typename Dest>
inline void operator()(Src &&src, Dest &&dest) const¶ Invoke the managed callback.
The parameters are unserialized from the input byte stream
src
. After the callback invocation, the result is serialized intodest
.The following member functions are also defined through CRTP.
All these functions work the same way asauto operator()(Input &&, Output &&); auto operator()(Input &&, Output &&) const;
operator()(Src &&, Dest &&)
.- Template Parameters:
Input – Input byte stream type of any kind
Output – Output byte stream type of any kind
- Parameters:
src – Byte getter
dest – Byte putter
-
inline std::size_t input_size() const¶
Get the size in bytes of the payload needed to invoke the wrapped callback.
- Returns:
The size of the payload in bytes
-
inline std::size_t output_size() const¶
Get the size in bytes of the payload representing the return value of the wrapped callback.
- Returns:
The size of the payload in bytes
-
template<typename F, F Ftor, endianess Endianess, signed_mode Signed_Mode>