You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

330 lines
12 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "Publisher.hpp"
#include <condition_variable>
#include <csignal>
#include <stdexcept>
#include <thread>
#include <sstream>
#include <iomanip>
#include <cstdio>
#include <fastdds/dds/domain/DomainParticipantFactory.hpp>
#include <fastdds/dds/log/Log.hpp>
#include <fastdds/dds/publisher/DataWriter.hpp>
#include <fastdds/dds/publisher/Publisher.hpp>
#include <fastdds/dds/publisher/qos/DataWriterQos.hpp>
#include <fastdds/dds/publisher/qos/PublisherQos.hpp>
#include <fastdds/rtps/common/Types.hpp>
#include <fastdds/dds/core/Types.hpp>
#include "WeighingDDSTypePubSubTypes.hpp"
#include "msg.hpp"
#include "MsgHandler.hpp"
#include "DEBUG.hpp"
using namespace eprosima::fastdds::dds;
PublisherApp::PublisherApp(
const int& domain_id)
: factory_(nullptr)
, participant_(nullptr)
, publisher_(nullptr)
, topic_(nullptr)
, writer_(nullptr)
, type_(new WeighingSystem::ReadCardRspPubSubType())
, matched_(0)
, samples_sent_(0)
, stop_(false)
, queue_trigger_publisher_(nullptr)
, queue_trigger_topic_(nullptr)
, queue_trigger_writer_(nullptr)
, queue_trigger_type_(new Queue::QueueTriggerPubSubType())
, current_date_()
, sequence_num_(0)
, last_card_()
, current_message_id_()
, wait_end_time_(std::chrono::steady_clock::now() - std::chrono::seconds(10)) // 初始化为过去时间,启动时不等待
, last_trigger_card_()
, wait_active_(false) // 启动后不处于等待期,由 OnCardDetected 激活
{
// Create the participant
DomainParticipantQos pqos = PARTICIPANT_QOS_DEFAULT;
pqos.name("ReadCard_pub_participant");
pqos.wire_protocol().builtin.discovery_config.leaseDuration = Duration_t(60, 0);
pqos.wire_protocol().builtin.discovery_config.leaseDuration_announcementperiod = Duration_t(30, 0);
factory_ = DomainParticipantFactory::get_shared_instance();
participant_ = factory_->create_participant(domain_id, pqos, nullptr, StatusMask::none());
if (participant_ == nullptr)
{
throw std::runtime_error("ReadCard Participant initialization failed");
}
// Register ReadCardRsp type
type_.register_type(participant_);
// Create the publisher (for ReadCardRsp)
PublisherQos pub_qos = PUBLISHER_QOS_DEFAULT;
participant_->get_default_publisher_qos(pub_qos);
publisher_ = participant_->create_publisher(pub_qos, nullptr, StatusMask::none());
if (publisher_ == nullptr)
{
throw std::runtime_error("ReadCard Publisher initialization failed");
}
// Create the topic (ReadCardRsp)
TopicQos topic_qos = TOPIC_QOS_DEFAULT;
participant_->get_default_topic_qos(topic_qos);
topic_ = participant_->create_topic("ReadCardRsp", type_.get_type_name(), topic_qos);
if (topic_ == nullptr)
{
throw std::runtime_error("ReadCard Topic initialization failed");
}
// Create the data writer (ReadCardRsp)
DataWriterQos writer_qos = DATAWRITER_QOS_DEFAULT;
publisher_->get_default_datawriter_qos(writer_qos);
writer_qos.reliability().kind = ReliabilityQosPolicyKind::RELIABLE_RELIABILITY_QOS;
writer_qos.durability().kind = DurabilityQosPolicyKind::VOLATILE_DURABILITY_QOS;
writer_qos.history().kind = HistoryQosPolicyKind::KEEP_LAST_HISTORY_QOS;
writer_ = publisher_->create_datawriter(topic_, writer_qos, this, StatusMask::all());
if (writer_ == nullptr)
{
throw std::runtime_error("ReadCard DataWriter initialization failed");
}
// ── 创建 QueueTrigger 发布者 ──
queue_trigger_type_.register_type(participant_);
PublisherQos qt_pub_qos = PUBLISHER_QOS_DEFAULT;
participant_->get_default_publisher_qos(qt_pub_qos);
queue_trigger_publisher_ = participant_->create_publisher(qt_pub_qos, nullptr, StatusMask::none());
TopicQos qt_topic_qos = TOPIC_QOS_DEFAULT;
participant_->get_default_topic_qos(qt_topic_qos);
queue_trigger_topic_ = participant_->create_topic(
"QueueTrigger",
queue_trigger_type_.get_type_name(),
qt_topic_qos);
DataWriterQos qt_writer_qos = DATAWRITER_QOS_DEFAULT;
queue_trigger_publisher_->get_default_datawriter_qos(qt_writer_qos);
qt_writer_qos.reliability().kind = RELIABLE_RELIABILITY_QOS;
qt_writer_qos.durability().kind = VOLATILE_DURABILITY_QOS;
qt_writer_qos.history().kind = KEEP_LAST_HISTORY_QOS;
queue_trigger_writer_ = queue_trigger_publisher_->create_datawriter(
queue_trigger_topic_, qt_writer_qos, this, StatusMask::all());
}
PublisherApp::~PublisherApp()
{
if (nullptr != participant_)
{
participant_->delete_contained_entities();
factory_->delete_participant(participant_);
}
}
void PublisherApp::on_publication_matched(
DataWriter* writer,
const PublicationMatchedStatus& info)
{
if (info.current_count_change == 1)
{
{
std::lock_guard<std::mutex> lock(mutex_);
matched_ = info.current_count;
}
DEBUG(writer->get_topic()->get_name() << " Publisher matched.");
cv_.notify_one();
}
else if (info.current_count_change == -1)
{
{
std::lock_guard<std::mutex> lock(mutex_);
matched_ = info.current_count;
}
DEBUG(writer->get_topic()->get_name() << " Publisher unmatched.");
}
}
void PublisherApp::run(std::shared_ptr<MsgHandler> handler)
{
// ── 拉卡检测回调:替代方案下放给主循环处理,这里不再绑定 ──
// 真正的触发逻辑在 run() 主循环里:
// HandleDeviceMsg → 把卡号写入 handler->pending_trigger_card_
// 主循环检测字段非空 → PublishQueueTrigger + PauseAutoFind + 启动 3s 等待期
// Device->Core 的响应DDS 载荷index + map<string,sequence<octet>>
WeighingSystem::ReadCardRsp rsp;
while (!is_stopped())
{
if (handler->isOpen() == true)
{
// ── 处理设备消息FindCard 响应会更新 m_CardId 并写入 pending_trigger_card_ ──
if (handler->HandleDeviceMsg(rsp))
{
// 当前版本不主动读卡,设备消息主要是 FindCard 响应
rsp.msg().clear();
}
// ── 检测新卡:发 QueueTrigger + 启动 3s 等待期 ──
std::string cardToTrigger;
{
std::lock_guard<std::mutex> lock(handler->trigger_mtx_);
if (!handler->pending_trigger_card_.empty())
{
cardToTrigger = handler->pending_trigger_card_;
handler->pending_trigger_card_.clear(); // 消费掉
}
}
if (!cardToTrigger.empty())
{
// 防止同一张卡短时间内重复触发3 秒等待期内忽略)
auto now = std::chrono::steady_clock::now();
if (now < wait_end_time_ && cardToTrigger == last_trigger_card_)
{
DEBUG("Card " << cardToTrigger
<< " still in 3s wait period, skipping QueueTrigger");
}
else
{
PublishQueueTrigger(cardToTrigger);
// 暂停自动寻卡state 切到 AUTO_FINDm_AutoFind=false
// 阻止 3 秒等待期内 HandleDdsMsg 接收 read/write
handler->PauseAutoFind();
wait_end_time_ = now + std::chrono::seconds(10);
wait_active_ = true; // 标记等待期,主循环在 3s 后才调 ForceReset
last_trigger_card_ = cardToTrigger;
DEBUG("QueueTrigger sent for card=" << cardToTrigger
<< ", waiting 10s before resuming auto-find");
}
}
// ── 等待期检查 ──
auto now2 = std::chrono::steady_clock::now();
if (now2 >= wait_end_time_)
{
// 等待期刚结束:仅在这一次调用时恢复 AUTO_FIND
if (wait_active_)
{
static auto last_resume_log = std::chrono::steady_clock::time_point{};
if (last_resume_log.time_since_epoch().count() == 0
|| now2 - last_resume_log > std::chrono::seconds(60))
{
DEBUG("10s wait elapsed, resuming AUTO_FIND");
last_resume_log = now2;
}
handler->ForceResetToAutoFind();
wait_active_ = false;
}
// pending read response来自超时跳过
if (handler->HasPendingReadResponse())
{
auto pendingRsp = handler->GetPendingReadResponse();
if (!pendingRsp.empty())
{
rsp.msg()["read"] = pendingRsp;
rsp.index(handler->GetCurrentIndex());
DEBUG("Publishing pending read response (from timeout skip)");
writer_->write(&rsp);
rsp.msg().clear();
}
}
handler->AutoFindTick();
}
else
{
// ── 等待期内:不发 DDS、不触发读卡、不恢复自动寻卡 ──
auto remaining = std::chrono::duration_cast<std::chrono::milliseconds>(
wait_end_time_ - now2).count();
}
}
std::unique_lock<std::mutex> period_lock(mutex_);
cv_.wait_for(period_lock, std::chrono::milliseconds(10), [this]()
{
return is_stopped();
});
}
}
bool PublisherApp::is_stopped()
{
return stop_.load();
}
void PublisherApp::on_data_available(DataReader* /*reader*/)
{
// 不再订阅任何 DDS Topic无需处理
}
void PublisherApp::on_subscription_matched(
DataReader* /*reader*/,
const SubscriptionMatchedStatus& /*info*/)
{
// 不再订阅任何 DDS Topic无需处理
}
void PublisherApp::stop()
{
stop_.store(true);
cv_.notify_one();
}
std::string PublisherApp::GenerateMessageId(const std::string& card_number)
{
std::lock_guard<std::mutex> lock(id_mtx_);
time_t now = time(nullptr);
char date_buf[9];
strftime(date_buf, sizeof(date_buf), "%Y%m%d", localtime(&now));
std::string today(date_buf);
// 日期变了:重置序号,清空上次的 card 和 message_id
if (today != current_date_)
{
current_date_ = today;
sequence_num_ = 0;
last_card_.clear();
current_message_id_.clear();
}
// 同一天、相同卡号:复用上次的 message_id
if (!last_card_.empty() && card_number == last_card_ && !current_message_id_.empty())
{
DEBUG("GenerateMessageId: card unchanged (" << card_number << "), reuse " << current_message_id_);
return current_message_id_;
}
// 不同卡号:序号累加
sequence_num_++;
last_card_ = card_number;
char id[32];
snprintf(id, sizeof(id), "qm-%s-%06u", today.c_str(), sequence_num_);
current_message_id_ = std::string(id);
DEBUG("GenerateMessageId: new card=" << card_number
<< " -> " << current_message_id_);
return current_message_id_;
}
void PublisherApp::PublishQueueTrigger(const std::string& card_number)
{
Queue::QueueTrigger msg;
const std::string mid = GenerateMessageId(card_number);
msg.message_id(mid);
msg.card_number(card_number);
msg.device_id("3"); // 本机读卡器设备编号
msg.trigger_time(""); // 暂时为 null空字符串
queue_trigger_writer_->write(&msg);
DEBUG("Published QueueTrigger: message_id=" << mid
<< " card=" << card_number << " device_id=3");
}