Files
MicrOBU/microbu-esp32c5/firmware/main/link_service.cpp
T
Ashin Walpola 0e9525162d Keep the colleague's microbu-esp32c5 tree in this repository
obu-firmware builds against vanetza-idf from microbu-esp32c5/external, but
that tree was gitignored, so a clone of this repository could not build the
firmware it ships. It is now committed here as ordinary files in its own
folder, microbu-esp32c5/: the colleague's commit cf4b99f plus the V2X2MAP
bridge's signature verification (--trust) used on the bench. Nothing is
fetched from or pushed to the colleague's repository; this repository and
its remotes carry everything. The folder's own .gitignore keeps build output,
downloaded components and private key material out, as it did there; the
committed file set is identical to that repository's tracked files.

The ESP32-C5 is still flashed from obu-firmware/, which only takes
vanetza-idf from microbu-esp32c5/, so the two stay separate folders.
FLASHING.md says how to take a newer version of the colleague's tree (copy
it over the folder, rebuild, test, commit).
2026-09-23 17:46:40 +02:00

156 lines
6.7 KiB
C++

#include "link_service.hpp"
#include "simple_ble.hpp"
#include "serial_link.hpp"
#include <esp_log.h>
#include <esp_timer.h>
namespace microbu {
namespace {
const char* TAG = "link";
}
LinkService::LinkService(Station& station) : station_(station) {
station_.on_indication([this](const link::BtpDataIndication& indication) {
broadcast(link::Opcode::BTP_DATA_INDICATION, ++own_sequence_, link::encode(indication));
});
station_.on_id_event([this](const link::IdChangeEvent& event) {
// PREPARE/COMMIT expect SF_IDCHANGE_EVENT_RESPONSE with this sequence
broadcast(link::Opcode::SF_IDCHANGE_EVENT, ++own_sequence_, link::encode(event));
});
}
void LinkService::send(LinkTransport transport, link::Opcode opcode, std::uint16_t sequence, const link::Bytes& body) {
link::Bytes octets;
link::Message message {{opcode, 0, sequence}, body};
if (!link::encode(message, octets)) { ESP_LOGE(TAG, "message 0x%02x too long (%u)", unsigned(opcode), unsigned(body.size())); return; }
if (transport == LinkTransport::serial) serial::write(serial::FrameType::LINK, octets);
else ble::write(octets);
}
void LinkService::broadcast(link::Opcode opcode, std::uint16_t sequence, const link::Bytes& body) {
send(LinkTransport::serial, opcode, sequence, body);
if (ble::connected()) send(LinkTransport::ble, opcode, sequence, body);
}
void LinkService::reply(LinkTransport transport, std::uint16_t sequence, link::Code code, const link::Bytes& detail) {
send(transport, link::Opcode::RESULT, sequence, link::encode(link::Result {code, detail}));
}
link::Status LinkService::status() {
auto value = station_.status();
const auto serial_counters = serial::counters();
const auto ble_counters = ble::counters();
value.link_rx_frames = serial_counters.frames + ble_counters.rx_messages;
value.link_crc_errors = serial_counters.crc_errors;
value.link_malformed += ble_counters.malformed;
return value;
}
void LinkService::handle(const link::Bytes& octets, LinkTransport origin) {
link::Message m;
if (!link::decode(octets, m)) return;
const auto seq = m.header.sequence;
using link::Opcode; using link::Code;
switch (m.header.opcode) {
case Opcode::STATION_CONFIGURE: {
link::StationConfigure c;
if (!link::decode(m.body, c)) return reply(origin, seq, Code::malformed);
link::Bytes detail;
const auto result = station_.configure(c, detail);
ESP_LOGI(TAG, "station configured: security %u, radio %u, result %d", unsigned(c.security), unsigned(c.radio), int(result));
return reply(origin, seq, result, detail);
}
case Opcode::POTI_UPDATE: {
link::PotiUpdate p;
if (!link::decode(m.body, p)) return reply(origin, seq, Code::malformed);
const auto result = station_.poti(p);
if (result != Code::accepted) reply(origin, seq, result); // accepted updates are silent
return;
}
case Opcode::BTP_DATA_REQUEST: {
link::BtpDataRequest q;
if (!link::decode(m.body, q)) return reply(origin, seq, Code::malformed);
return reply(origin, seq, station_.btp_request(q));
}
case Opcode::CREDENTIALS_PROVISION: {
link::CredentialsProvision c;
if (!link::decode(m.body, c)) return reply(origin, seq, Code::malformed);
if (c.offset == 0) { bundle_.clear(); bundle_total_ = c.total_length; }
if (c.total_length != bundle_total_ || c.offset != bundle_.size() || c.total_length > 8192) { bundle_.clear(); return reply(origin, seq, Code::malformed); }
bundle_.insert(bundle_.end(), c.segment.begin(), c.segment.end());
if (bundle_.size() < bundle_total_) return reply(origin, seq, Code::accepted);
link::ApplyReport report;
link::Code result = Code::accepted;
try {
result = station_.provision(bundle_, report);
} catch (const std::exception& e) {
ESP_LOGE(TAG, "provision exception: %s", e.what());
result = Code::invalid_argument;
}
bundle_.clear();
link::Bytes detail;
detail.push_back(static_cast<std::uint8_t>(report.roots));
detail.push_back(static_cast<std::uint8_t>(report.authorities));
detail.push_back(static_cast<std::uint8_t>(report.tickets));
return reply(origin, seq, result, detail);
}
case Opcode::CREDENTIALS_ERASE:
return reply(origin, seq, station_.erase_credentials());
case Opcode::SF_IDCHANGE_SUBSCRIBE: {
link::Reader r(m.body);
const auto data = r.bytes(r.u8());
if (!r.done()) {
ESP_LOGW(TAG, "SF_IDCHANGE_SUBSCRIBE malformed (len=%u)", (unsigned)m.body.size());
return reply(origin, seq, Code::malformed);
}
std::uint64_t handle = 0;
const auto result = station_.subscribe(data, handle);
ESP_LOGI(TAG, "SF_IDCHANGE_SUBSCRIBE handle=%llu result=%d", (unsigned long long)handle, int(result));
link::Writer w; w.u64(handle);
return reply(origin, seq, result, result == Code::accepted ? w.out : link::Bytes {});
}
case Opcode::SF_IDCHANGE_UNSUBSCRIBE: {
link::Reader r(m.body);
const auto handle = r.u64();
if (!r.done()) return reply(origin, seq, Code::malformed);
return reply(origin, seq, station_.unsubscribe(handle));
}
case Opcode::SF_IDCHANGE_EVENT_RESPONSE: {
link::IdChangeEventResponse e;
if (!link::decode(m.body, e)) return; // no reply defined for a response
station_.event_response(e.subscription, e.return_code != 0);
return;
}
case Opcode::SF_IDCHANGE_TRIGGER:
return reply(origin, seq, station_.trigger());
case Opcode::SF_ID_LOCK: {
link::Reader r(m.body);
const auto seconds = r.u8();
if (!r.done()) return reply(origin, seq, Code::malformed);
std::uint64_t handle = 0;
const auto result = station_.lock(seconds, handle);
link::Writer w; w.u64(handle);
return reply(origin, seq, result, result == Code::accepted ? w.out : link::Bytes {});
}
case Opcode::SF_ID_UNLOCK: {
link::Reader r(m.body);
const auto handle = r.u64();
if (!r.done()) return reply(origin, seq, Code::malformed);
return reply(origin, seq, station_.unlock(handle));
}
case Opcode::STATUS_REQUEST:
return send(origin, link::Opcode::STATUS, seq, link::encode(status()));
default:
return reply(origin, seq, Code::unknown_opcode);
}
}
void LinkService::tick() {
const auto now = esp_timer_get_time();
if (now - last_status_us_ < 1000000) return;
last_status_us_ = now;
broadcast(link::Opcode::STATUS, ++own_sequence_, link::encode(status()));
}
} // namespace microbu