Keep vanetza-idf in obu-firmware, so a plain clone builds the firmware
obu-firmware builds against the vanetza-idf C-ITS library, which until now came from the colleague's microbu-esp32c5 tree beside the repository and was not tracked here, so a clone of this repository could not build the firmware it ships. The library alone is now part of obu-firmware, as obu-firmware/external/vanetza-idf: their external/vanetza-idf at commit cf4b99f, unchanged (9775 files; see its PROVENANCE.md). CMake takes it from there by default; -DVANETZA_IDF_DIR still points the build elsewhere. The rest of the colleague's tree (their own VAM firmware, PKI tooling, station-link Python tools, the V2X2MAP bridge) stays out of this repository and gitignored; nothing is pushed to their repository. NOTES.md, docs/06, TODO.md and the pcap verifier's usage line point at the new location.
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
if(VANETZA_WITH_RPC)
|
||||
message(STATUS "Enabled Vanetza RPC feature")
|
||||
set(CAPNP_VERSION_STAMP "${CMAKE_CURRENT_BINARY_DIR}/capnp_version.stamp")
|
||||
file(WRITE "${CAPNP_VERSION_STAMP}.tmp" "${CapnProto_VERSION}")
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${CAPNP_VERSION_STAMP}.tmp" "${CAPNP_VERSION_STAMP}")
|
||||
capnp_generate_cpp(CAPNP_SOURCES CAPNP_HEADERS vanetza.capnp)
|
||||
add_custom_command(OUTPUT ${CAPNP_SOURCES} ${CAPNP_HEADERS}
|
||||
APPEND DEPENDS "${CAPNP_VERSION_STAMP}")
|
||||
add_vanetza_component(rpc
|
||||
asio_event_port.cpp
|
||||
asio_stream.cpp
|
||||
link_layer_client.cpp
|
||||
${CAPNP_SOURCES} ${CAPNP_HEADERS})
|
||||
target_include_directories(rpc PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_link_libraries(rpc PUBLIC dcc)
|
||||
target_link_libraries(rpc PUBLIC Boost::headers CapnProto::capnp-rpc)
|
||||
else()
|
||||
message(STATUS "Disabled Vanetza RPC feature")
|
||||
endif()
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include <vanetza/rpc/asio_event_port.hpp>
|
||||
#include <kj/async.h>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace rpc
|
||||
{
|
||||
|
||||
class AsioEventLoop : public kj::EventLoop
|
||||
{
|
||||
public:
|
||||
AsioEventLoop(AsioEventPort& port) : kj::EventLoop(port)
|
||||
{
|
||||
port.setLoop(this);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace rpc
|
||||
} // namespace vanetza
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "vanetza/rpc/asio_event_port.hpp"
|
||||
|
||||
#include <boost/asio/post.hpp>
|
||||
#include <chrono>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace rpc
|
||||
{
|
||||
|
||||
AsioEventPort::AsioEventPort(boost::asio::io_context& io) :
|
||||
io_(io),
|
||||
steady_timer_(io_),
|
||||
clock_(kj::systemPreciseMonotonicClock()),
|
||||
timer_(clock_.now())
|
||||
{
|
||||
}
|
||||
|
||||
bool AsioEventPort::wait()
|
||||
{
|
||||
io_.run_one();
|
||||
advanceTime();
|
||||
armTimeout();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AsioEventPort::poll()
|
||||
{
|
||||
io_.poll();
|
||||
advanceTime();
|
||||
armTimeout();
|
||||
return false;
|
||||
}
|
||||
|
||||
void AsioEventPort::setRunnable(bool runnable)
|
||||
{
|
||||
if (runnable) {
|
||||
boost::asio::post(io_, [this]() {
|
||||
auto* loop = loop_.load(std::memory_order_acquire);
|
||||
if (loop && loop->isRunnable()) {
|
||||
loop->run();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void AsioEventPort::advanceTime()
|
||||
{
|
||||
timer_.advanceTo(clock_.now());
|
||||
}
|
||||
|
||||
void AsioEventPort::armTimeout()
|
||||
{
|
||||
std::chrono::nanoseconds dt {
|
||||
timer_.timeoutToNextEvent(clock_.now(), kj::NANOSECONDS, kj::maxValue)
|
||||
.map([](uint64_t ns) { return ns; })
|
||||
.orDefault(0)
|
||||
};
|
||||
if (dt > std::chrono::nanoseconds::zero()) {
|
||||
steady_timer_.expires_after(dt);
|
||||
steady_timer_.async_wait([this](const boost::system::error_code& ec) {
|
||||
if (!ec) {
|
||||
advanceTime();
|
||||
armTimeout();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rpc
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include <kj/async.h>
|
||||
#include <kj/timer.h>
|
||||
#include <atomic>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace rpc
|
||||
{
|
||||
|
||||
class AsioEventPort : public kj::EventPort
|
||||
{
|
||||
public:
|
||||
AsioEventPort(boost::asio::io_context& io);
|
||||
|
||||
bool wait() override;
|
||||
bool poll() override;
|
||||
void setRunnable(bool runnable) override;
|
||||
|
||||
void setLoop(kj::EventLoop* loop)
|
||||
{
|
||||
loop_.store(loop, std::memory_order_release);
|
||||
}
|
||||
|
||||
kj::Timer& getTimer()
|
||||
{
|
||||
return timer_;
|
||||
}
|
||||
|
||||
private:
|
||||
void advanceTime();
|
||||
void armTimeout();
|
||||
|
||||
boost::asio::io_context& io_;
|
||||
boost::asio::steady_timer steady_timer_;
|
||||
std::atomic<kj::EventLoop*> loop_ { nullptr };
|
||||
const kj::MonotonicClock& clock_;
|
||||
kj::TimerImpl timer_;
|
||||
};
|
||||
|
||||
} // namespace rpc
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,155 @@
|
||||
#include <vanetza/common/annotation.hpp>
|
||||
#include <vanetza/rpc/asio_stream.hpp>
|
||||
|
||||
#include <boost/asio/buffer.hpp>
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/write.hpp>
|
||||
#include <kj/debug.h>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace rpc
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class KjBufferSequence
|
||||
{
|
||||
public:
|
||||
using value_type = boost::asio::const_buffer;
|
||||
|
||||
class const_iterator
|
||||
{
|
||||
public:
|
||||
using value_type = boost::asio::const_buffer;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = const value_type*;
|
||||
using reference = value_type;
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
|
||||
const_iterator() = default;
|
||||
explicit const_iterator(const kj::ArrayPtr<const kj::byte>* ptr) : ptr_(ptr) {}
|
||||
|
||||
value_type operator*() const { return boost::asio::const_buffer(ptr_->begin(), ptr_->size()); }
|
||||
const_iterator& operator++()
|
||||
{
|
||||
++ptr_;
|
||||
return *this;
|
||||
}
|
||||
const_iterator operator++(int)
|
||||
{
|
||||
auto tmp = *this;
|
||||
++ptr_;
|
||||
return tmp;
|
||||
}
|
||||
bool operator==(const const_iterator& other) const { return ptr_ == other.ptr_; }
|
||||
bool operator!=(const const_iterator& other) const { return ptr_ != other.ptr_; }
|
||||
|
||||
private:
|
||||
const kj::ArrayPtr<const kj::byte>* ptr_ = nullptr;
|
||||
};
|
||||
|
||||
explicit KjBufferSequence(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) : pieces_(pieces) {}
|
||||
|
||||
const_iterator begin() const { return const_iterator(pieces_.begin()); }
|
||||
const_iterator end() const { return const_iterator(pieces_.end()); }
|
||||
|
||||
private:
|
||||
kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
AsioStream::AsioStream(boost::asio::ip::tcp::socket socket) : socket_(std::move(socket))
|
||||
{
|
||||
}
|
||||
|
||||
void AsioStream::shutdownWrite()
|
||||
{
|
||||
socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_send);
|
||||
}
|
||||
|
||||
kj::Promise<void> AsioStream::write(const void* buffer, size_t size)
|
||||
{
|
||||
auto paf = kj::newPromiseAndFulfiller<void>();
|
||||
boost::asio::const_buffer buf(buffer, size);
|
||||
boost::asio::async_write(socket_, buf,
|
||||
[this, fulfiller = std::move(paf.fulfiller)](
|
||||
const boost::system::error_code& ec, std::size_t bytes_transferred) mutable {
|
||||
mark_unused(bytes_transferred);
|
||||
if (ec) {
|
||||
signalDisconnect(ec);
|
||||
fulfiller->reject(KJ_EXCEPTION(FAILED, "write", ec.message()));
|
||||
} else {
|
||||
fulfiller->fulfill();
|
||||
}
|
||||
});
|
||||
|
||||
return kj::mv(paf.promise);
|
||||
}
|
||||
|
||||
kj::Promise<void> AsioStream::write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces)
|
||||
{
|
||||
auto paf = kj::newPromiseAndFulfiller<void>();
|
||||
boost::asio::async_write(socket_, KjBufferSequence(pieces),
|
||||
[this, fulfiller = std::move(paf.fulfiller)](
|
||||
const boost::system::error_code& ec, std::size_t bytes_transferred) mutable {
|
||||
mark_unused(bytes_transferred);
|
||||
if (ec) {
|
||||
signalDisconnect(ec);
|
||||
fulfiller->reject(KJ_EXCEPTION(FAILED, "write", ec.message()));
|
||||
} else {
|
||||
fulfiller->fulfill();
|
||||
}
|
||||
});
|
||||
return kj::mv(paf.promise);
|
||||
}
|
||||
|
||||
kj::Promise<void> AsioStream::whenWriteDisconnected()
|
||||
{
|
||||
if (!socket_.is_open()) {
|
||||
return kj::READY_NOW;
|
||||
}
|
||||
KJ_IF_MAYBE(p, disconnect_promise_) {
|
||||
return p->addBranch();
|
||||
} else {
|
||||
auto paf = kj::newPromiseAndFulfiller<void>();
|
||||
disconnect_fulfiller_ = kj::mv(paf.fulfiller);
|
||||
auto fork = paf.promise.fork();
|
||||
auto result = fork.addBranch();
|
||||
disconnect_promise_ = kj::mv(fork);
|
||||
return kj::mv(result);
|
||||
}
|
||||
}
|
||||
|
||||
void AsioStream::signalDisconnect(const boost::system::error_code& ec)
|
||||
{
|
||||
if (ec != boost::asio::error::operation_aborted) {
|
||||
boost::system::error_code ignored;
|
||||
socket_.close(ignored);
|
||||
KJ_IF_MAYBE(f, disconnect_fulfiller_) {
|
||||
(*f)->fulfill();
|
||||
disconnect_fulfiller_ = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kj::Promise<size_t> AsioStream::tryRead(void* buffer, size_t minBytes, size_t maxBytes)
|
||||
{
|
||||
auto paf = kj::newPromiseAndFulfiller<size_t>();
|
||||
boost::asio::async_read(socket_, boost::asio::buffer(buffer, maxBytes), boost::asio::transfer_at_least(minBytes),
|
||||
[this, fulfiller = std::move(paf.fulfiller)](
|
||||
const boost::system::error_code& ec, std::size_t bytes_transferred) mutable {
|
||||
if (ec) {
|
||||
signalDisconnect(ec);
|
||||
fulfiller->reject(KJ_EXCEPTION(FAILED, "read", ec.message()));
|
||||
} else {
|
||||
fulfiller->fulfill(kj::mv(bytes_transferred));
|
||||
}
|
||||
});
|
||||
return kj::mv(paf.promise);
|
||||
}
|
||||
|
||||
} // namespace rpc
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
#include <kj/async-io.h>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace rpc
|
||||
{
|
||||
|
||||
class AsioStream : public kj::AsyncIoStream
|
||||
{
|
||||
public:
|
||||
AsioStream(boost::asio::ip::tcp::socket socket);
|
||||
|
||||
void shutdownWrite() override;
|
||||
kj::Promise<void> write(const void* buffer, size_t size) override;
|
||||
kj::Promise<void> write(kj::ArrayPtr<const kj::ArrayPtr<const kj::byte>> pieces) override;
|
||||
kj::Promise<void> whenWriteDisconnected() override;
|
||||
kj::Promise<size_t> tryRead(void* buffer, size_t minBytes, size_t maxBytes) override;
|
||||
|
||||
private:
|
||||
void signalDisconnect(const boost::system::error_code& ec);
|
||||
|
||||
boost::asio::ip::tcp::socket socket_;
|
||||
kj::Maybe<kj::ForkedPromise<void>> disconnect_promise_;
|
||||
kj::Maybe<kj::Own<kj::PromiseFulfiller<void>>> disconnect_fulfiller_;
|
||||
};
|
||||
|
||||
} // namepsace rpc
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,299 @@
|
||||
#include <vanetza/access/data_request.hpp>
|
||||
#include <vanetza/access/pppp.hpp>
|
||||
#include <vanetza/net/packet_variant.hpp>
|
||||
#include <vanetza/rpc/link_layer_client.hpp>
|
||||
#include <vanetza/rpc/logger.hpp>
|
||||
|
||||
#include <capnp/rpc-twoparty.h>
|
||||
#include <capnp/rpc.h>
|
||||
#include <kj/async.h>
|
||||
#include <kj/time.h>
|
||||
#include "vanetza.capnp.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace rpc
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
LinkLayerClient::ErrorCode map_error_code(vanetza::rpc::LinkLayer::ErrorCode in)
|
||||
{
|
||||
switch (in)
|
||||
{
|
||||
case LinkLayer::ErrorCode::OK:
|
||||
return LinkLayerClient::ErrorCode::Ok;
|
||||
case LinkLayer::ErrorCode::INVALID_ARGUMENT:
|
||||
return LinkLayerClient::ErrorCode::InvalidArgument;
|
||||
case LinkLayer::ErrorCode::UNSUPPORTED:
|
||||
return LinkLayerClient::ErrorCode::Unsupported;
|
||||
case LinkLayer::ErrorCode::INTERNAL_ERROR:
|
||||
default:
|
||||
return LinkLayerClient::ErrorCode::InternalError;
|
||||
};
|
||||
}
|
||||
|
||||
class DataListener : public vanetza::rpc::LinkLayer::DataListener::Server
|
||||
{
|
||||
public:
|
||||
DataListener(std::function<void(LinkLayerClient::Indication)> callback) :
|
||||
callback_(callback)
|
||||
{
|
||||
}
|
||||
|
||||
kj::Promise<void> onDataIndication(OnDataIndicationContext context) override
|
||||
{
|
||||
auto frame = context.getParams().getFrame();
|
||||
vanetza::ByteBuffer payload { frame.getPayload().begin(), frame.getPayload().end() };
|
||||
LinkLayerClient::Indication indication { std::move(payload) };
|
||||
assign(indication.source, frame.getSourceAddress());
|
||||
assign(indication.destination, frame.getDestinationAddress());
|
||||
if (context.getParams().hasRxParams()) {
|
||||
if (context.getParams().getRxParams().isWlan()) {
|
||||
indication.technology = LinkLayerClient::Technology::ITS_G5;
|
||||
} else if (context.getParams().getRxParams().isCv2x()) {
|
||||
indication.technology = LinkLayerClient::Technology::LTE_V2X;
|
||||
}
|
||||
}
|
||||
callback_(std::move(indication));
|
||||
return kj::READY_NOW;
|
||||
}
|
||||
|
||||
void assign(MacAddress& into, const capnp::Data::Reader& from)
|
||||
{
|
||||
if (from.size() == MacAddress::length_bytes)
|
||||
{
|
||||
std::copy(from.begin(), from.end(), into.octets.begin());
|
||||
}
|
||||
else if (from.size() < MacAddress::length_bytes)
|
||||
{
|
||||
auto it = std::next(into.octets.begin(), MacAddress::length_bytes - from.size());
|
||||
std::fill(into.octets.begin(), it, 0);
|
||||
std::copy(from.begin(), from.end(), it);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto it = std::next(from.begin(), from.size() - MacAddress::length_bytes);
|
||||
std::copy(it, from.end(), into.octets.begin());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(LinkLayerClient::Indication)> callback_;
|
||||
};
|
||||
|
||||
class CbrListener : public vanetza::rpc::LinkLayer::CbrListener::Server
|
||||
{
|
||||
public:
|
||||
CbrListener(std::function<void(dcc::ChannelLoad)> callback) :
|
||||
callback_(callback)
|
||||
{
|
||||
}
|
||||
|
||||
kj::Promise<void> onCbrReport(OnCbrReportContext context) override
|
||||
{
|
||||
auto cbr = context.getParams().getCbr();
|
||||
dcc::ChannelLoad channel_load;
|
||||
if (cbr.getSamples() > 0 && cbr.getBusy() > 0) {
|
||||
if (cbr.getSamples() >= cbr.getBusy()) {
|
||||
channel_load = dcc::ChannelLoad(cbr.getBusy(), cbr.getSamples());
|
||||
} else {
|
||||
channel_load = dcc::ChannelLoad(cbr.getSamples(), cbr.getSamples());
|
||||
}
|
||||
};
|
||||
callback_(channel_load);
|
||||
return kj::READY_NOW;
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(dcc::ChannelLoad)> callback_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
LinkLayerClient::Indication::Indication(vanetza::ByteBuffer buffer) :
|
||||
packet(std::move(buffer), OsiLayer::Network)
|
||||
{
|
||||
}
|
||||
|
||||
class LinkLayerClient::Context : public kj::TaskSet::ErrorHandler
|
||||
{
|
||||
public:
|
||||
Context(kj::Timer& timer, kj::AsyncIoStream& connection, Logger* logger) :
|
||||
logger_(logger),
|
||||
timer_(timer),
|
||||
task_set_(*this),
|
||||
client_(connection),
|
||||
link_layer_(client_.bootstrap().castAs<vanetza::rpc::LinkLayer>())
|
||||
{
|
||||
}
|
||||
|
||||
void taskFailed(kj::Exception&& exception) override
|
||||
{
|
||||
VANETZA_RPC_LOG_ERROR(logger_, "LinkLayerClient/task", exception.getDescription().cStr());
|
||||
}
|
||||
|
||||
void addTask(kj::Promise<void>&& promise, kj::Duration timeout)
|
||||
{
|
||||
task_set_.add(timer_.timeoutAfter(timeout, kj::mv(promise)));
|
||||
}
|
||||
|
||||
Logger* logger_ = nullptr;
|
||||
kj::Timer& timer_;
|
||||
kj::TaskSet task_set_;
|
||||
capnp::TwoPartyClient client_;
|
||||
vanetza::rpc::LinkLayer::Client link_layer_;
|
||||
};
|
||||
|
||||
LinkLayerClient::LinkLayerClient(kj::Timer& timer, kj::AsyncIoStream& connection, Logger* logger) :
|
||||
context_(std::make_unique<Context>(timer, connection, logger))
|
||||
{
|
||||
auto rx_data = context_->link_layer_.subscribeDataRequest();
|
||||
rx_data.setListener(kj::heap<DataListener>(std::bind(&LinkLayerClient::do_indicate, this, std::placeholders::_1)));
|
||||
context_->addTask(rx_data.send().ignoreResult(), 1 * kj::SECONDS);
|
||||
|
||||
auto cbr = context_->link_layer_.subscribeCbrRequest();
|
||||
cbr.setListener(kj::heap<CbrListener>(std::bind(&LinkLayerClient::do_report, this, std::placeholders::_1)));
|
||||
context_->addTask(cbr.send().ignoreResult(), 1 * kj::SECONDS);
|
||||
}
|
||||
|
||||
LinkLayerClient::~LinkLayerClient()
|
||||
{
|
||||
}
|
||||
|
||||
void LinkLayerClient::configure(Technology technology)
|
||||
{
|
||||
VANETZA_RPC_LOG_DEBUG(context_->logger_, "LinkLayerClient/configure", stringify(technology));
|
||||
technology_ = technology;
|
||||
}
|
||||
|
||||
void LinkLayerClient::add_task(kj::Promise<void>&& promise)
|
||||
{
|
||||
VANETZA_RPC_LOG_DEBUG(context_->logger_, "LinkLayerClient/task", "add");
|
||||
context_->task_set_.add(kj::mv(promise));
|
||||
}
|
||||
|
||||
kj::Promise<LinkLayerClient::Identity> LinkLayerClient::identify()
|
||||
{
|
||||
auto ident_request = context_->link_layer_.identifyRequest();
|
||||
auto promise = ident_request.send().then(
|
||||
[](capnp::Response<rpc::LinkLayer::IdentifyResults>&& results) mutable -> kj::Promise<Identity> {
|
||||
Identity identity;
|
||||
identity.id = results.getId();
|
||||
identity.version = results.getVersion();
|
||||
if (results.hasInfo()) {
|
||||
identity.info = results.getInfo().cStr();
|
||||
}
|
||||
return identity;
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
void LinkLayerClient::request(const access::DataRequest& request, std::unique_ptr<ChunkPacket> packet)
|
||||
{
|
||||
auto tx_data = context_->link_layer_.transmitDataRequest();
|
||||
|
||||
auto frame = tx_data.initFrame();
|
||||
frame.setSourceAddress(kj::ArrayPtr<const kj::byte> { request.source_addr.octets.data(), request.source_addr.octets.size() });
|
||||
frame.setDestinationAddress(kj::ArrayPtr<const kj::byte> { request.destination_addr.octets.data(), request.destination_addr.octets.size() });
|
||||
auto payload_view = create_byte_view(*packet, OsiLayer::Network, OsiLayer::Application);
|
||||
vanetza::ByteBuffer payload { payload_view.begin(), payload_view.end() };
|
||||
frame.setPayload(kj::ArrayPtr<const kj::byte> { payload.data(), payload.size() });
|
||||
|
||||
auto tx_params = tx_data.initTxParams();
|
||||
if (technology_ == Technology::ITS_G5) {
|
||||
auto wlan_tx_params = tx_params.initWlan();
|
||||
wlan_tx_params.setPriority(access::user_priority(request.access_category));
|
||||
} else if (technology_ == Technology::LTE_V2X) {
|
||||
auto cv2x_tx_params = tx_params.initCv2x();
|
||||
cv2x_tx_params.setPriority(access::pppp_from_ac(request.access_category));
|
||||
}
|
||||
|
||||
auto promise = tx_data.send().then([this](capnp::Response<vanetza::rpc::LinkLayer::TransmitDataResults>&& results) -> kj::Promise<void> {
|
||||
if (results.getError() != vanetza::rpc::LinkLayer::ErrorCode::OK) {
|
||||
VANETZA_RPC_LOG_ERROR(context_->logger_, "LinkLayerClient/request", stringify(map_error_code(results.getError())));
|
||||
} else {
|
||||
VANETZA_RPC_LOG_DEBUG(context_->logger_, "LinkLayerClient/request", "ok");
|
||||
}
|
||||
return kj::READY_NOW;
|
||||
});
|
||||
context_->addTask(kj::mv(promise), 100 * kj::MILLISECONDS);
|
||||
}
|
||||
|
||||
void LinkLayerClient::do_indicate(Indication indication)
|
||||
{
|
||||
VANETZA_RPC_LOG_DEBUG(context_->logger_, "LinkLayerClient/indicate", stringify(indication.technology))
|
||||
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||
if (indication_callback_) {
|
||||
indication_callback_(std::move(indication));
|
||||
}
|
||||
}
|
||||
|
||||
void LinkLayerClient::do_report(dcc::ChannelLoad cl)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||
if (cbr_callback_) {
|
||||
cbr_callback_(cl);
|
||||
}
|
||||
}
|
||||
|
||||
void LinkLayerClient::indicate(IndicationCallback callback)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||
indication_callback_ = callback;
|
||||
}
|
||||
|
||||
void LinkLayerClient::report_channel_load(ChannelLoadReportCallback callback)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(callback_mutex_);
|
||||
cbr_callback_ = callback;
|
||||
}
|
||||
|
||||
kj::Promise<LinkLayerClient::ErrorCode> LinkLayerClient::set_source_address(const MacAddress& addr)
|
||||
{
|
||||
auto request = context_->link_layer_.setSourceAddressRequest();
|
||||
auto msg_addr = request.initAddress(MacAddress::length_bytes);
|
||||
std::copy(addr.octets.begin(), addr.octets.end(), msg_addr.begin());
|
||||
|
||||
using Response = capnp::Response<vanetza::rpc::LinkLayer::SetSourceAddressResults>;
|
||||
kj::ForkedPromise<ErrorCode> forked = request.send().then([this](Response&& response) -> kj::Promise<ErrorCode> {
|
||||
auto result = map_error_code(response.getError());
|
||||
VANETZA_RPC_LOG_DEBUG(context_->logger_, "LinkLayerClient/SetSourceAddress", stringify(result));
|
||||
return result;
|
||||
}).fork();
|
||||
context_->addTask(forked.addBranch().ignoreResult(), 500 * kj::MILLISECONDS);
|
||||
return forked.addBranch();
|
||||
}
|
||||
|
||||
const char* stringify(LinkLayerClient::ErrorCode ec)
|
||||
{
|
||||
using ErrorCode = LinkLayerClient::ErrorCode;
|
||||
static const std::array<const char*, 4> strings = { "ok", "invalid argument", "unsupported", "internal error" };
|
||||
static_assert(static_cast<std::size_t>(ErrorCode::Ok) == 0, "ErrorCode 'ok' is at index 0");
|
||||
const auto idx = static_cast<std::size_t>(ec);
|
||||
if (idx >= strings.size()) {
|
||||
return "unknown";
|
||||
} else {
|
||||
return strings[idx];
|
||||
}
|
||||
}
|
||||
|
||||
const char* stringify(LinkLayerClient::Technology tech)
|
||||
{
|
||||
using Tech = LinkLayerClient::Technology;
|
||||
if (tech == Tech::ITS_G5) {
|
||||
return "ITS-G5";
|
||||
} else if (tech == Tech::LTE_V2X) {
|
||||
return "LTE-V2X";
|
||||
} else if (tech == Tech::Unspecified) {
|
||||
return "unspecified";
|
||||
} else {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rpc
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,90 @@
|
||||
#pragma once
|
||||
#include <kj/async-io.h>
|
||||
#include <vanetza/dcc/channel_load.hpp>
|
||||
#include <vanetza/net/chunk_packet.hpp>
|
||||
#include <vanetza/net/cohesive_packet.hpp>
|
||||
#include <vanetza/net/mac_address.hpp>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
|
||||
namespace access { class DataRequest; }
|
||||
|
||||
namespace rpc
|
||||
{
|
||||
|
||||
class Logger;
|
||||
|
||||
class LinkLayerClient
|
||||
{
|
||||
public:
|
||||
enum class ErrorCode
|
||||
{
|
||||
Ok,
|
||||
InvalidArgument,
|
||||
Unsupported,
|
||||
InternalError,
|
||||
};
|
||||
|
||||
enum class Technology
|
||||
{
|
||||
Unspecified,
|
||||
ITS_G5,
|
||||
LTE_V2X,
|
||||
};
|
||||
|
||||
struct Indication
|
||||
{
|
||||
Indication(vanetza::ByteBuffer);
|
||||
|
||||
vanetza::MacAddress source;
|
||||
vanetza::MacAddress destination;
|
||||
vanetza::CohesivePacket packet;
|
||||
Technology technology = Technology::Unspecified;
|
||||
};
|
||||
|
||||
struct Identity
|
||||
{
|
||||
std::uint64_t id = 0;
|
||||
std::uint32_t version = 0;
|
||||
std::string info;
|
||||
};
|
||||
|
||||
using IndicationCallback = std::function<void(Indication)>;
|
||||
using ChannelLoadReportCallback = std::function<void(dcc::ChannelLoad)>;
|
||||
|
||||
LinkLayerClient(kj::Timer&, kj::AsyncIoStream&, Logger* = nullptr);
|
||||
~LinkLayerClient();
|
||||
|
||||
kj::Promise<Identity> identify();
|
||||
void request(const access::DataRequest&, std::unique_ptr<ChunkPacket>);
|
||||
void indicate(IndicationCallback callback);
|
||||
void report_channel_load(ChannelLoadReportCallback callback);
|
||||
kj::Promise<ErrorCode> set_source_address(const MacAddress&);
|
||||
|
||||
void configure(Technology);
|
||||
void add_task(kj::Promise<void>&&);
|
||||
|
||||
private:
|
||||
class Context;
|
||||
|
||||
void do_indicate(Indication);
|
||||
void do_report(dcc::ChannelLoad);
|
||||
|
||||
std::unique_ptr<Context> context_;
|
||||
std::mutex callback_mutex_;
|
||||
IndicationCallback indication_callback_;
|
||||
ChannelLoadReportCallback cbr_callback_;
|
||||
Technology technology_ = Technology::Unspecified;
|
||||
};
|
||||
|
||||
const char* stringify(LinkLayerClient::ErrorCode);
|
||||
const char* stringify(LinkLayerClient::Technology);
|
||||
|
||||
} // namespace rpc
|
||||
} // namespace vanetza
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
namespace vanetza
|
||||
{
|
||||
namespace rpc
|
||||
{
|
||||
|
||||
class Logger
|
||||
{
|
||||
public:
|
||||
virtual void error(const char* module, const char* message) = 0;
|
||||
virtual void debug(const char* module, const char* message) = 0;
|
||||
|
||||
virtual ~Logger() = default;
|
||||
};
|
||||
|
||||
#define VANETZA_RPC_LOG_ERROR(logger, module, message) \
|
||||
if (logger != nullptr) { \
|
||||
logger->error(module, message); \
|
||||
}
|
||||
#define VANETZA_RPC_LOG_DEBUG(logger, module, message) \
|
||||
if (logger != nullptr) { \
|
||||
logger->debug(module, message); \
|
||||
}
|
||||
|
||||
} // namespace rpc
|
||||
} // namespace vanetza
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import asyncio
|
||||
import capnp
|
||||
import math
|
||||
import os
|
||||
import time
|
||||
|
||||
capnp.remove_import_hook()
|
||||
vanetza_capnp = capnp.load('vanetza.capnp', 'Vanteza RPC', ['/usr/include/', '/usr/local/include/'])
|
||||
|
||||
class PeriodicTask:
|
||||
def __init__(self, timeout):
|
||||
self._timeout = timeout
|
||||
self._task = asyncio.create_task(self._do_loop())
|
||||
|
||||
async def _do_loop(self):
|
||||
while asyncio.get_running_loop().is_running():
|
||||
await asyncio.sleep(self._timeout)
|
||||
await self.action()
|
||||
|
||||
def cancel(self):
|
||||
self._task.cancel()
|
||||
|
||||
async def action(self):
|
||||
pass
|
||||
|
||||
|
||||
class PeriodicCbrGenerator(PeriodicTask):
|
||||
def __init__(self, callback):
|
||||
super().__init__(timeout=0.1)
|
||||
self._callback = callback
|
||||
|
||||
async def action(self):
|
||||
if callable(self._callback):
|
||||
await self._callback(0.3 + 0.25 * math.sin(0.2*math.pi*time.monotonic()))
|
||||
|
||||
|
||||
class PeriodicDataGenerator(PeriodicTask):
|
||||
def __init__(self, callback):
|
||||
super().__init__(timeout=2.5)
|
||||
self._callback = callback
|
||||
|
||||
async def action(self):
|
||||
if callable(self._callback):
|
||||
await self._callback()
|
||||
|
||||
|
||||
class Server(vanetza_capnp.LinkLayer.Server):
|
||||
def __init__(self):
|
||||
self._cbr = PeriodicCbrGenerator(self._notify_cbr)
|
||||
self._data = PeriodicDataGenerator(self._notify_data)
|
||||
|
||||
def stop(self):
|
||||
self._cbr.cancel()
|
||||
self._data.cancel()
|
||||
|
||||
async def _notify_cbr(self, channel_load: float):
|
||||
if self._cbr_listener:
|
||||
cbr = {
|
||||
'busy': int(channel_load * 1000),
|
||||
'samples': 1000
|
||||
}
|
||||
await self._cbr_listener.onCbrReport(cbr=cbr)
|
||||
|
||||
async def _notify_data(self):
|
||||
if self._data_listener:
|
||||
frame = {
|
||||
'sourceAddress': b"\xde\xad\xc0\xff\xff\xee",
|
||||
'destinationAddress': b"\xff\xff\xff\xff\xff\xff",
|
||||
'payload': b"Vanetza"
|
||||
}
|
||||
rx = {
|
||||
'wlan': { 'power': -60 * 8, 'datarate': 6 * 2 }
|
||||
}
|
||||
await self._data_listener.onDataIndication(frame=frame, rxParams=rx)
|
||||
|
||||
|
||||
async def identify(self, **kwargs):
|
||||
return (os.getpid(), 1)
|
||||
|
||||
async def transmitData(self, frame, txParams, **kwargs):
|
||||
print("client requested data transmission")
|
||||
return vanetza_capnp.LinkLayer.ErrorCode.ok
|
||||
|
||||
async def subscribeData(self, listener, **kwargs):
|
||||
print("client subscribed data indications")
|
||||
self._data_listener = listener
|
||||
|
||||
async def subscribeCbr(self, listener, **kwargs):
|
||||
print("client subscribed CBR reports")
|
||||
self._cbr_listener = listener
|
||||
|
||||
async def setSourceAddress(self, address, **kwargs):
|
||||
print(f"client set source address: {address}")
|
||||
if (len(address) != 6):
|
||||
return vanetza_capnp.LinkLayer.ErrorCode.invalidArgument
|
||||
else:
|
||||
return vanetza_capnp.LinkLayer.ErrorCode.ok
|
||||
|
||||
async def new_connection(stream):
|
||||
server = Server()
|
||||
await capnp.TwoPartyServer(stream, bootstrap=server).on_disconnect()
|
||||
server.stop()
|
||||
|
||||
async def main():
|
||||
server = await capnp.AsyncIoStream.create_server(new_connection, '*', '23057')
|
||||
async with server:
|
||||
await server.serve_forever()
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(capnp.run(main()))
|
||||
@@ -0,0 +1,97 @@
|
||||
@0xcb50be3531badf53;
|
||||
|
||||
using Cxx = import "/capnp/c++.capnp";
|
||||
$Cxx.namespace("vanetza::rpc");
|
||||
|
||||
interface LinkLayer
|
||||
{
|
||||
struct Frame
|
||||
{
|
||||
sourceAddress @0 :Data;
|
||||
destinationAddress @1 :Data;
|
||||
payload @2 :Data;
|
||||
}
|
||||
|
||||
struct WlanParameters
|
||||
{
|
||||
# Parameters for WLAN devices in OCB mode (IEEE 802.11 p and bd)
|
||||
priority @0 :UInt8; # 802.1 user priority (0-7)
|
||||
power @1 :Int16; # dBm scaled by 8
|
||||
datarate @2 :UInt16; # Mbps scaled by 2 (500kbps steps)
|
||||
}
|
||||
|
||||
struct Cv2xParameters
|
||||
{
|
||||
# Parameters for C-V2X devices (LTE-V2X and 5G-V2X)
|
||||
priority @0 :UInt8; # PPPP (0-7)
|
||||
power @1 :Int16; # dBm scaled by 8
|
||||
}
|
||||
|
||||
struct TxParameters
|
||||
{
|
||||
union
|
||||
{
|
||||
unspecified @0 :Void;
|
||||
wlan @1 :WlanParameters;
|
||||
cv2x @2 :Cv2xParameters;
|
||||
}
|
||||
}
|
||||
|
||||
struct RxParameters
|
||||
{
|
||||
union
|
||||
{
|
||||
unspecified @0 :Void;
|
||||
wlan @1 :WlanParameters;
|
||||
cv2x @2 :Cv2xParameters;
|
||||
}
|
||||
|
||||
timestamp :union
|
||||
{
|
||||
none @3 :Void;
|
||||
hardware @4 :UInt64;
|
||||
# time stamp accurately generated by hardware
|
||||
software @5 :UInt64;
|
||||
# time stamp added by software (slightly inaccurate)
|
||||
}
|
||||
}
|
||||
|
||||
interface DataListener
|
||||
{
|
||||
onDataIndication @0 (frame: Frame, rxParams :RxParameters);
|
||||
}
|
||||
|
||||
interface CbrListener
|
||||
{
|
||||
onCbrReport @0 (cbr :ChannelBusyRatio);
|
||||
}
|
||||
|
||||
struct ChannelBusyRatio
|
||||
{
|
||||
busy @0 :UInt16; # number of samples sensed as busy
|
||||
samples @1 :UInt16; # total number of samples in measurement interval
|
||||
}
|
||||
|
||||
enum ErrorCode
|
||||
{
|
||||
ok @0;
|
||||
invalidArgument @1;
|
||||
unsupported @2;
|
||||
internalError @3;
|
||||
}
|
||||
|
||||
identify @0 () -> (id :UInt64, version :UInt32, info :Text);
|
||||
# lookup identify of link layer device
|
||||
|
||||
transmitData @1 (frame :Frame, txParams :TxParameters) -> (error :ErrorCode, message :Text);
|
||||
# request transmission of a data frame
|
||||
|
||||
subscribeData @2 (listener :DataListener);
|
||||
# subscribe to received data frames
|
||||
|
||||
subscribeCbr @3 (listener :CbrListener);
|
||||
# subscribe to channel busy ratio reports
|
||||
|
||||
setSourceAddress @4 (address :Data) -> (error :ErrorCode);
|
||||
# set (own) source address of link layer (for ACKs)
|
||||
}
|
||||
Reference in New Issue
Block a user