Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions lib/include/kickcat/Mailbox.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,14 @@ namespace kickcat::mailbox::response
/// \brief Synchronous single-shot processing: dispatch, process, and return the reply
std::vector<uint8_t> processRequest(std::vector<uint8_t>&& raw_message);

/// \brief Serve an ETG.8200 gateway request synchronously against this OD and return
/// a completed GatewayMessage carrying the reply, ready to be consumed by
/// Gateway::processPendingRequests(). Intended for the master-side (ETG.1510) path.
/// \return nullptr if the request is malformed (too small to contain a mailbox header)
/// or if the dictionary produced no/malformed reply.
std::shared_ptr<mailbox::request::GatewayMessage> createGatewayMessage(
uint8_t const* raw_message, int32_t raw_message_size, uint16_t gateway_index);

// Access on the next message to send: mainly for unit test
std::vector<uint8_t> const& readyToSend() const { return to_send_.front(); }

Expand Down
7 changes: 7 additions & 0 deletions lib/master/include/kickcat/Bus.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ namespace kickcat
/// \return nullptr if message cannot be added (malformed, bad address, unsupported protocol, etc.), a handle on the message otherwise
std::shared_ptr<mailbox::request::GatewayMessage> addGatewayMessage(uint8_t const* raw_message, int32_t raw_message_size, uint16_t gateway_index);

/// \brief Install a master-side response mailbox to serve ETG.1510 (address == 0) gateway requests.
/// \details The bus does not take ownership: the caller keeps the mailbox alive for as long as
/// the bus may route requests to it. Pass nullptr to detach.
void setMasterMailbox(mailbox::response::Mailbox* mbx) { master_mailbox_ = mbx; }

void clearErrorCounters();

// Helpers for broadcast commands, mainly for init purpose
Expand Down Expand Up @@ -240,6 +245,8 @@ namespace kickcat
Slave* dc_slave_{nullptr};

MailboxStatusFMMU mailbox_status_fmmu_{MailboxStatusFMMU::NONE};

mailbox::response::Mailbox* master_mailbox_{nullptr};
};

/**
Expand Down
4 changes: 4 additions & 0 deletions lib/master/include/kickcat/Slave.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ namespace kickcat
{
void parseSII(uint8_t const* data, std::size_t size);

/// \brief Human-readable slave name. Returns the SII general-category device name when present,
/// otherwise a fallback derived from the fixed station address (e.g. "Slave @0x1001").
std::string name() const;

ErrorCounters const& errorCounters() const;
int computeErrorCounters() const;

Expand Down
12 changes: 8 additions & 4 deletions lib/master/src/Bus.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1331,12 +1331,16 @@ namespace kickcat
mailbox::Header mbx_header;
std::memcpy(&mbx_header, raw_message, sizeof(mailbox::Header));

// Try to associate the request with a destination
// Try to associate the request with a destination.
// Per ETG.1510 section 6, address == 0 in an ETG.8200 request addresses the master OD.
if (mbx_header.address == 0)
{
// Master is the destination, unsupported for now (ETG 1510)
bus_error("Master mailbox not implemented");
return nullptr;
if (master_mailbox_ == nullptr)
{
bus_error("Master mailbox not implemented");
return nullptr;
}
return master_mailbox_->createGatewayMessage(raw_message, raw_message_size, gateway_index);
}

auto it = std::find_if(slaves_.begin(), slaves_.end(), [&](Slave const& slave) { return slave.address == mbx_header.address; });
Expand Down
58 changes: 44 additions & 14 deletions lib/master/src/MasterOD.cc
Original file line number Diff line number Diff line change
Expand Up @@ -72,27 +72,57 @@ namespace kickcat
auto const& sii = slave.sii;
uint16_t index = 0x8000 + i;

// Slave name: ETG.1510 :03 references the SII general category name string.
// Slave::name() handles the SII lookup and falls back to a station-address label.
std::string slave_name = slave.name();
uint16_t name_bitlen = static_cast<uint16_t>(slave_name.size() * 8);

// ETG.1510 Table 9 rows :36 Link Preset / :37 Flags are ENI-derived. KickCAT has no ENI
// ingestion yet, so expose conformant zero defaults (no redundancy, no hot-connect group,
// no explicit expected physical links). Override once ENI lands.
uint8_t const link_preset = 0;
uint8_t const flags = 0;

CoE::Object obj{index, CoE::ObjectCode::RECORD, "Slave Configuration", {}};
CoE::addEntry<uint8_t> (obj, 0, 8, 0, CoE::Access::READ, CoE::DataType::UNSIGNED8, "Number of Entries", 7);
CoE::addEntry<uint16_t>(obj, 1, 16, 8, CoE::Access::READ, CoE::DataType::UNSIGNED16, "Fixed Station Address", slave.address);
CoE::addEntry<uint32_t>(obj, 5, 32, 24, CoE::Access::READ, CoE::DataType::UNSIGNED32, "Vendor Id", sii.info.vendor_id);
CoE::addEntry<uint32_t>(obj, 6, 32, 56, CoE::Access::READ, CoE::DataType::UNSIGNED32, "Product Code", sii.info.product_code);
CoE::addEntry<uint32_t>(obj, 7, 32, 88, CoE::Access::READ, CoE::DataType::UNSIGNED32, "Revision Number", sii.info.revision_number);
CoE::addEntry<uint32_t>(obj, 8, 32, 120, CoE::Access::READ, CoE::DataType::UNSIGNED32, "Serial Number", sii.info.serial_number);
CoE::addEntry<uint16_t>(obj, 33, 16, 152, CoE::Access::READ, CoE::DataType::UNSIGNED16, "Mailbox Out Size", sii.info.standard_recv_mbx_size);
CoE::addEntry<uint16_t>(obj, 34, 16, 168, CoE::Access::READ, CoE::DataType::UNSIGNED16, "Mailbox In Size", sii.info.standard_send_mbx_size);

// Subindex 0 is the highest supported subindex (CANopen convention), not a count.
uint16_t bit_offset = 0;
CoE::addEntry<uint8_t> (obj, 0, 8, bit_offset, CoE::Access::READ, CoE::DataType::UNSIGNED8, "Number of Entries", uint8_t{39});
bit_offset += 8;
CoE::addEntry<uint16_t>(obj, 1, 16, bit_offset, CoE::Access::READ, CoE::DataType::UNSIGNED16, "Fixed Station Address", slave.address);
bit_offset += 16;
// ETG.1510 Table 9 has no :02 entry; jump from :01 to :03.
CoE::addEntry<char const*>(obj, 3, name_bitlen, bit_offset, CoE::Access::READ, CoE::DataType::VISIBLE_STRING, "Name", slave_name.c_str());
bit_offset += name_bitlen;
CoE::addEntry<uint32_t>(obj, 5, 32, bit_offset, CoE::Access::READ, CoE::DataType::UNSIGNED32, "Vendor Id", sii.info.vendor_id);
bit_offset += 32;
CoE::addEntry<uint32_t>(obj, 6, 32, bit_offset, CoE::Access::READ, CoE::DataType::UNSIGNED32, "Product Code", sii.info.product_code);
bit_offset += 32;
CoE::addEntry<uint32_t>(obj, 7, 32, bit_offset, CoE::Access::READ, CoE::DataType::UNSIGNED32, "Revision Number", sii.info.revision_number);
bit_offset += 32;
CoE::addEntry<uint32_t>(obj, 8, 32, bit_offset, CoE::Access::READ, CoE::DataType::UNSIGNED32, "Serial Number", sii.info.serial_number);
bit_offset += 32;
CoE::addEntry<uint16_t>(obj, 33, 16, bit_offset, CoE::Access::READ, CoE::DataType::UNSIGNED16, "Mailbox Out Size", sii.info.standard_recv_mbx_size);
bit_offset += 16;
CoE::addEntry<uint16_t>(obj, 34, 16, bit_offset, CoE::Access::READ, CoE::DataType::UNSIGNED16, "Mailbox In Size", sii.info.standard_send_mbx_size);
bit_offset += 16;
CoE::addEntry<uint8_t> (obj, 36, 8, bit_offset, CoE::Access::READ, CoE::DataType::UNSIGNED8, "Link Preset", link_preset);
bit_offset += 8;
CoE::addEntry<uint8_t> (obj, 37, 8, bit_offset, CoE::Access::READ, CoE::DataType::UNSIGNED8, "Flags", flags);
bit_offset += 8;
CoE::addEntry<uint16_t>(obj, 39, 16, bit_offset, CoE::Access::READ, CoE::DataType::UNSIGNED16, "Mailbox Protocols Supported", sii.info.mailbox_protocol);

dict.push_back(std::move(obj));

auto& entries = dict.back().entries;
ConfigurationData config;
config.fixed_address = &entries[1];
config.vendor_id = &entries[2];
config.product_code = &entries[3];
config.revision = &entries[4];
config.serial_number = &entries[5];
config.mailbox_out_size = &entries[6];
config.mailbox_in_size = &entries[7];
config.vendor_id = &entries[3];
config.product_code = &entries[4];
config.revision = &entries[5];
config.serial_number = &entries[6];
config.mailbox_out_size = &entries[7];
config.mailbox_in_size = &entries[8];

configuration_data_.push_back(config);
}
Expand Down
21 changes: 21 additions & 0 deletions lib/master/src/Slave.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <algorithm>
#include <cstdio>
#include <iomanip>

#include "Slave.h"
Expand All @@ -22,6 +23,26 @@ namespace kickcat
mailbox_bootstrap.send_size = sii.info.bootstrap_send_mbx_size;
}

std::string Slave::name() const
{
uint8_t name_idx = sii.general.device_name_id;
if ((name_idx > 0) and (name_idx <= sii.strings.size()))
{
auto const& s = sii.strings[name_idx - 1];
if (not s.empty())
{
return s;
}
}

// Fallback: derive a label from the fixed station address so operators can still correlate
// the slave with what they see on the wire.
char buf[16];
std::snprintf(buf, sizeof(buf), "Slave @0x%04X", address);
return buf;
}


ErrorCounters const& Slave::errorCounters() const
{
return error_counters;
Expand Down
34 changes: 34 additions & 0 deletions lib/src/Mailbox.cc
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,40 @@ namespace kickcat::mailbox::response
}


std::shared_ptr<mailbox::request::GatewayMessage> Mailbox::createGatewayMessage(
uint8_t const* raw_message, int32_t raw_message_size, uint16_t gateway_index)
{
// Guard against malformed or truncated requests before treating the buffer as a mailbox frame.
// Covers both negative sizes and "shorter than the mailbox header" sizes in one check.
if (raw_message_size < static_cast<int32_t>(sizeof(mailbox::Header)))
{
return nullptr;
}

std::vector<uint8_t> request(raw_message, raw_message + raw_message_size);
auto reply = processRequest(std::move(request));
if (reply.size() < sizeof(mailbox::Header))
{
// Empty or malformed reply (the dictionary or a factory produced nothing routable).
return nullptr;
}

// Wrap the reply and drive it to SUCCESS by tagging its header with the gateway-masked
// address and feeding it back through GatewayMessage::process(). process() then restores
// the original (master) address on the delivered frame and flips status to SUCCESS, which
// is what Gateway::processPendingRequests() looks for.
uint16_t mailbox_size = static_cast<uint16_t>(
std::max<size_t>(reply.size(), static_cast<size_t>(raw_message_size)));
auto msg = std::make_shared<mailbox::request::GatewayMessage>(mailbox_size, raw_message, gateway_index, 0ms);

auto* reply_header = reinterpret_cast<mailbox::Header*>(reply.data());
reply_header->address = mailbox::GATEWAY_MESSAGE_MASK | gateway_index;
msg->process(reply.data());

return msg;
}


void Mailbox::send()
{
SyncManager sync;
Expand Down
1 change: 1 addition & 0 deletions unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ add_executable(kickcat_unit src/adler32_sum-t.cc
src/mailbox/CoE/request-t.cc
src/mailbox/CoE/response-t.cc
src/masterOD-t.cc
src/masterOD-gateway-t.cc
src/slave/slave-t.cc
src/slave/PDO-t.cc
src/Time.cc
Expand Down
Loading
Loading