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:
Ashin Walpola
2026-09-24 10:56:05 +02:00
parent 2f60623e18
commit d107534eb2
9781 changed files with 1560475 additions and 17 deletions
@@ -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)
}