Skip to content

Commit 27bb144

Browse files
authored
Merge pull request #226 from 107-systems/reg-storage
Support permanent register value storage/retrieval.
2 parents 80025d1 + d8660c8 commit 27bb144

File tree

7 files changed

+206
-4
lines changed

7 files changed

+206
-4
lines changed

src/107-Arduino-Cyphal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@
1818
#include "Subscription.hpp"
1919
#include "ServiceClient.hpp"
2020
#include "ServiceServer.hpp"
21+
#include "util/storage/register_storage.hpp"
2122

2223
#endif /* _107_ARDUINO_CYPHAL_H_ */

src/ServiceClient.ipp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ template<typename T_REQ, typename T_RSP, typename OnResponseCb>
6767
bool ServiceClient<T_REQ, T_RSP, OnResponseCb>::onTransferReceived(CanardRxTransfer const & transfer)
6868
{
6969
/* Deserialize the response message. */
70-
T_RSP rsp{};
70+
T_RSP rsp;
7171
nunavut::support::const_bitspan rsp_bitspan(static_cast<uint8_t *>(transfer.payload), transfer.payload_size);
7272
auto const rc = deserialize(rsp, rsp_bitspan);
7373
if (!rc) return false;

src/ServiceServer.ipp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ template<typename T_REQ, typename T_RSP, typename OnRequestCb>
3737
bool ServiceServer<T_REQ, T_RSP, OnRequestCb>::onTransferReceived(CanardRxTransfer const & transfer)
3838
{
3939
/* Deserialize the request message. */
40-
T_REQ req{};
40+
T_REQ req;
4141
nunavut::support::const_bitspan req_buf_bitspan(static_cast<uint8_t *>(transfer.payload), transfer.payload_size);
4242
auto const req_rc = deserialize(req, req_buf_bitspan);
4343
if (!req_rc) return false;

src/Subscription.ipp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Subscription<T, OnReceiveCb>::~Subscription()
3636
template<typename T, typename OnReceiveCb>
3737
bool Subscription<T, OnReceiveCb>::onTransferReceived(CanardRxTransfer const & transfer)
3838
{
39-
T msg{};
39+
T msg;
4040
nunavut::support::const_bitspan msg_bitspan(static_cast<uint8_t *>(transfer.payload), transfer.payload_size);
4141
auto const rc = deserialize(msg, msg_bitspan);
4242
if (!rc) return false;

src/util/registry/Registry.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class Registry final : public registry::Registry
8181

8282
TAccessResponse onAccess_1_0_Request_Received(TAccessRequest const &req)
8383
{
84-
auto const req_name = std::string_view(reinterpret_cast<const char *>(req.name.name.cbegin()));
84+
auto const req_name = std::string_view(reinterpret_cast<const char *>(req.name.name.cbegin()), req.name.name.size());
8585

8686
/* Try to set the registers value. Note, if this is a RO register
8787
* this call will fail with SetError::Mutability.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* This software is distributed under the terms of the MIT License.
3+
* Copyright (c) 2020-2023 LXRobotics.
4+
* Author: Pavel Kirienko <[email protected]>
5+
* Contributors: https://github.com/107-systems/107-Arduino-Cyphal/graphs/contributors.
6+
*/
7+
8+
#pragma once
9+
10+
#if __GNUC__ >= 11
11+
12+
#include <string_view>
13+
#include <optional>
14+
#include <variant>
15+
16+
namespace cyphal::support::platform::storage
17+
{
18+
19+
enum class Error : std::uint8_t
20+
{
21+
Existence, ///< Entry does not exist but should; or exists but shouldn't.
22+
API, ///< Bad API invocation (e.g., null pointer).
23+
Capacity, ///< No space left on the storage device.
24+
IO, ///< Device input/output error.
25+
Internal, ///< Internal failure in the filesystem (storage corruption or logic error).
26+
};
27+
28+
namespace interface
29+
{
30+
31+
/// Key-value storage provides a very simple API for storing and retrieving named blobs.
32+
/// The underlying storage implementation is required to be power-loss tolerant and to
33+
/// validate data integrity per key (e.g., using CRC and such).
34+
/// This interface is fully blocking and should only be used during initialization and shutdown,
35+
/// never during normal operation. Non-blocking adapters can be built on top of it.
36+
class KeyValueStorage
37+
{
38+
public:
39+
KeyValueStorage() = default;
40+
KeyValueStorage(const KeyValueStorage&) = delete;
41+
KeyValueStorage(KeyValueStorage&&) = delete;
42+
auto operator=(const KeyValueStorage&) -> KeyValueStorage& = delete;
43+
auto operator=(KeyValueStorage&&) -> KeyValueStorage& = delete;
44+
virtual ~KeyValueStorage() = default;
45+
46+
/// The return value is the number of bytes read into the buffer or the error.
47+
[[nodiscard]] virtual auto get(const std::string_view key, const std::size_t size, void* const data) const
48+
-> std::variant<Error, std::size_t> = 0;
49+
50+
/// Existing data, if any, is replaced entirely. New file and its parent directories created implicitly.
51+
/// Either all or none of the data bytes are written.
52+
[[nodiscard]] virtual auto put(const std::string_view key, const std::size_t size, const void* const data)
53+
-> std::optional<Error> = 0;
54+
55+
/// Remove key. If the key does not exist, the existence error is returned.
56+
[[nodiscard]] virtual auto drop(const std::string_view key) -> std::optional<Error> = 0;
57+
};
58+
59+
} /* interface */
60+
61+
} /* cyphal::support::platform::storage */
62+
63+
#endif /* __GNUC__ >= 11 */
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/**
2+
* This software is distributed under the terms of the MIT License.
3+
* Copyright (c) 2020-2023 LXRobotics.
4+
* Author: Pavel Kirienko <[email protected]>
5+
* Contributors: https://github.com/107-systems/107-Arduino-Cyphal/graphs/contributors.
6+
*/
7+
8+
#pragma once
9+
10+
#if __GNUC__ >= 11
11+
12+
#include "KeyValueStorage.hpp"
13+
14+
#include <functional>
15+
16+
#include "../registry/registry_base.hpp"
17+
18+
namespace cyphal::support
19+
{
20+
21+
/// Scan all persistent registers in the registry and load their values from the storage if present.
22+
/// Each register is loaded from a separate file, the file name equals the name of the register (no extension).
23+
/// Stored registers that are not present in the registry will not be loaded.
24+
/// The serialization format is simply the Cyphal DSDL.
25+
/// In case of error, only part of the registers may be loaded and the registry will be left in an inconsistent state.
26+
[[nodiscard]] inline std::optional<platform::storage::Error> load(const platform::storage::interface::KeyValueStorage& kv,
27+
registry::IIntrospectableRegistry& rgy)
28+
{
29+
for (std::size_t index = 0; index < rgy.size(); index++)
30+
{
31+
// Find the next register in the registry.
32+
const auto reg_name_storage = rgy.index(index); // This is a little suboptimal but we don't care.
33+
const auto reg_name = std::string_view(reinterpret_cast<const char *>(reg_name_storage.name.cbegin()), reg_name_storage.name.size());
34+
if (reg_name.empty())
35+
{
36+
break; // No more registers to load.
37+
}
38+
// If we get nothing, this means that the register has disappeared from the storage.
39+
if (const auto reg_meta = rgy.get(reg_name); reg_meta && reg_meta.value().flags.persistent)
40+
{
41+
// We will attempt to restore the register even if it is not mutable,
42+
// as it is not incompatible with the protocol.
43+
std::array<std::uint8_t, uavcan::_register::Value_1_0::_traits_::SerializationBufferSizeBytes> serialized;
44+
const auto kv_get_result = kv.get(reg_name, serialized.size(), serialized.data());
45+
if (const auto* const err = std::get_if<platform::storage::Error>(&kv_get_result))
46+
{
47+
if (platform::storage::Error::Existence != *err)
48+
{
49+
return *err;
50+
}
51+
// The register is simply not present in the storage, which is OK.
52+
}
53+
else
54+
{
55+
registry::Value value;
56+
// Invalid data in the storage will be ignored.
57+
nunavut::support::const_bitspan serialized_bitspan(serialized.data(), serialized.size());
58+
auto const rc = deserialize(value, serialized_bitspan);
59+
if (rc)
60+
{
61+
// Assign the value to the register.
62+
// Shall it fail, the error is likely to be corrected during the next save().
63+
(void) rgy.set(reg_name, value);
64+
}
65+
}
66+
}
67+
}
68+
return std::nullopt;
69+
}
70+
71+
/// The register savior is the counterpart of load().
72+
/// Saves all persistent mutable registers from the registry to the storage.
73+
/// Registers that are not persistent OR not mutable will not be saved;
74+
/// the reason immutable registers are not saved is that they are assumed to be constant or runtime-computed,
75+
/// so there is no point wasting storage on them (which may be limited).
76+
/// Eventually this logic should be decoupled from the network register presentation facade by introducing more
77+
/// fine-grained register flags, such as "internally mutable" and "externally mutable".
78+
///
79+
/// Existing stored registers that are not found in the registry will not be altered.
80+
/// In case of failure, one failure handling strategy is to clear or reformat the entire storage and try again.
81+
///
82+
/// The removal predicate, if provided, allows the caller to specify which registers need to be removed from the
83+
/// storage instead of being saved. This is useful for implementing the "factory reset" feature.
84+
template <typename ResetPredicate>
85+
[[nodiscard]] std::optional<platform::storage::Error> save(platform::storage::interface::KeyValueStorage& kv,
86+
const registry::IIntrospectableRegistry& rgy,
87+
ResetPredicate const reset_predicate)
88+
{
89+
for (std::size_t index = 0; index < rgy.size(); index++)
90+
{
91+
const auto reg_name_storage = rgy.index(index); // This is a little suboptimal but we don't care.
92+
const auto reg_name = std::string_view(reinterpret_cast<const char *>(reg_name_storage.name.cbegin()), reg_name_storage.name.size());
93+
if (reg_name.empty())
94+
{
95+
break; // No more registers to load.
96+
}
97+
// Reset is handled before any other checks to enhance forward compatibility.
98+
if (reset_predicate(reg_name))
99+
{
100+
if (const auto err = kv.drop(reg_name); err && (err != platform::storage::Error::Existence))
101+
{
102+
return err;
103+
}
104+
}
105+
// If we get nothing, this means that the register has disappeared from the storage.
106+
// We do not save immutable registers because they are assumed to be constant, so no need to waste storage.
107+
else if (const auto reg_meta = rgy.get(reg_name);
108+
reg_meta && reg_meta.value().flags.persistent && reg_meta.value().flags.mutable_)
109+
{
110+
// Now we have the register and we know that it is persistent, so we can save it.
111+
std::array<std::uint8_t, uavcan::_register::Value_1_0::_traits_::SerializationBufferSizeBytes> serialized;
112+
nunavut::support::bitspan serialized_bitspan{serialized};
113+
auto const rc = serialize(reg_meta.value().value, serialized_bitspan);
114+
if (!rc)
115+
{
116+
std::abort(); // This should never happen.
117+
}
118+
if (const auto err = kv.put(reg_name, *rc, serialized.data()); err)
119+
{
120+
return err;
121+
}
122+
}
123+
else
124+
{
125+
(void) 0; // Nothing to do -- the register needs to be neither reset nor saved.
126+
}
127+
}
128+
return std::nullopt;
129+
}
130+
[[nodiscard]] inline std::optional<platform::storage::Error> save(platform::storage::interface::KeyValueStorage& kv,
131+
const registry::IIntrospectableRegistry& rgy)
132+
{
133+
return save(kv, rgy, [](std::string_view) { return false; });
134+
}
135+
136+
} /* namespace cyphal::support */
137+
138+
#endif /* __GNUC__ >= 11 */

0 commit comments

Comments
 (0)