Skip to content

Commit 17dbefa

Browse files
authored
Merge pull request #47 from KodlabPenn/lcm-multiple-subscriber
Lcm multiple subscriber
2 parents 18ec7eb + 8215dcf commit 17dbefa

File tree

6 files changed

+262
-84
lines changed

6 files changed

+262
-84
lines changed

examples/leg_2DoF_example.cpp

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,14 @@ class Hopping : public kodlab::mjbots::MjbotsControlLoop<LegLog, LegGains> {
101101
log_data_->hybrid_mode = mode_;
102102
}
103103

104-
void ProcessInput() override {
105-
kv_ = lcm_sub_.data_.kv;
106-
k_ = lcm_sub_.data_.k;
107-
k_stiff_ = lcm_sub_.data_.k_stiff;
108-
b_ = lcm_sub_.data_.b;
109-
b_stiff_ = lcm_sub_.data_.b_stiff;
110-
kp_ = lcm_sub_.data_.kp;
111-
kd_ = lcm_sub_.data_.kd;
104+
void ProcessInput(const LegGains &input_data) override {
105+
kv_ = input_data.kv;
106+
k_ = input_data.k;
107+
k_stiff_ = input_data.k_stiff;
108+
b_ = input_data.b;
109+
b_stiff_ = input_data.b_stiff;
110+
kp_ = input_data.kp;
111+
kd_ = input_data.kd;
112112
LOG_INFO(
113113
"Response received: { kv = % 5.2f, k = % 5.2f, k_stiff = % 5.2f, "
114114
"b = % 5.2f, b_stiff = % 5.2f, kp = % 5.2f, kd = % 5.2f }",

examples/robot_example.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ class SimpleRobotControlLoop : public kodlab::mjbots::MjbotsControlLoop<ManyMoto
4848
}
4949
}
5050

51-
void ProcessInput() override
51+
void ProcessInput(const ModeInput &input_data) override
5252
{
53-
robot_->mode = lcm_sub_.data_.mode;
53+
robot_->mode = input_data.mode;
5454
std::cout << "Switching to behavior " << robot_->mode << std::endl;
5555
// If the kill robot mode is detected kill robot using CTRL_C flag handler.
5656
if (robot_->mode == robot_->KILL_ROBOT)
@@ -73,6 +73,7 @@ int main(int argc, char **argv)
7373
// Define robot options
7474
kodlab::mjbots::ControlLoopOptions options;
7575
options.log_channel_name = "motor_data";
76+
options.input_channel_name = "mode_input";
7677
options.frequency = 1000;
7778
options.realtime_params.main_cpu = 3;
7879
options.realtime_params.can_cpu = 2;
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* @file lcm_message_handler.h
3+
* @author Ethan Musser ([email protected])
4+
* @brief Provides LCM message handler object to be derived or composed into
5+
* classes requiring LCM input via the `LcmSubscriber` object.
6+
* @date 8/6/22
7+
*
8+
* @copyright Copyright 2022 The Trustees of the University of Pennsylvania. All
9+
* rights reserved.
10+
*
11+
*/
12+
13+
#pragma once
14+
15+
#include <string>
16+
#include <mutex>
17+
#include <optional>
18+
#include "lcm/lcm-cpp.hpp"
19+
#include "real_time_tools/thread.hpp"
20+
21+
namespace kodlab {
22+
23+
/**
24+
* @brief Handler class for incoming LCM messages.
25+
* @details This class is intended to be derived from or composed into objects
26+
* requiring LCM input. This class and its children should be compatible with
27+
* the `LcmSubscriber` object, and can implemented with the
28+
* `LcmSubscriber::AddSubscription` method in particular.
29+
* @tparam Message LCM message type
30+
*/
31+
template<class Message>
32+
class LcmMessageHandler {
33+
34+
public:
35+
/**
36+
* @brief Constructs an `LcmMessageHandler` and ensures the mutex is unlocked.
37+
*/
38+
LcmMessageHandler() {
39+
mutex_.unlock(); // ensures mutex is unlocked
40+
}
41+
42+
/**
43+
* @brief Virtual destructor for clean inherited class destruction
44+
*/
45+
virtual ~LcmMessageHandler() = default;
46+
47+
/**
48+
* @brief Callback function for when an LCM message is received.
49+
* @details This function serves as a callback for incoming LCM messages on
50+
* `channel`. It copies the decoded message data to `data`, and sets
51+
* `new_message` to `true`.
52+
* @param rbuf LCM recieve buffer containing raw bytes and timestamp
53+
* @param channel channel name
54+
* @param msg pointer to the incoming message data
55+
*/
56+
void HandleMessage(const lcm::ReceiveBuffer *rbuf,
57+
const std::string &channel,
58+
const Message *msg) {
59+
mutex_.lock();
60+
data_ = *msg;
61+
new_message_ = true;
62+
mutex_.unlock();
63+
}
64+
65+
/**
66+
* @brief Retrieve new data, if available.
67+
* @details Retrieve a `std::optional` object containing new data if the
68+
* internal `new_message_` flag evaluates to `true`, or `std::nullopt_t`
69+
* otherwise. If new data is retrieved, the new message flag is reset to
70+
* `false`.
71+
* @return `std::optional` containing message data if new message available,
72+
* `std::nullopt_t` otherwise
73+
*/
74+
std::optional<Message> GetDataIfNew() {
75+
if (new_message_ && mutex_.try_lock()) {
76+
Message data_out = data_;
77+
new_message_ = false;
78+
mutex_.unlock();
79+
return data_out;
80+
}
81+
return {};
82+
}
83+
84+
/**
85+
* @brief Check if new message is available.
86+
* @return `true` if new message available, `false` otherwise
87+
*/
88+
[[nodiscard]] bool is_new_message() const { return new_message_; }
89+
90+
private:
91+
92+
/**
93+
* @brief Flag indicating that a new message is available.
94+
*/
95+
bool new_message_ = false;
96+
97+
/**
98+
* @brief Data for the most-recently received message.
99+
*/
100+
Message data_;
101+
102+
/**
103+
* @brief Mutex for the message handler.
104+
*/
105+
std::mutex mutex_;
106+
107+
};
108+
109+
} // kodlab
Lines changed: 121 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,148 @@
1-
// BSD 3-Clause License
2-
// Copyright (c) 2021 The Trustees of the University of Pennsylvania. All Rights Reserved
3-
// Authors:
4-
// Shane Rozen-Levy <[email protected]>
1+
/**
2+
* @file lcm_subscriber.h
3+
* @author Ethan Musser ([email protected])
4+
* @author J. Diego Caporale ([email protected])
5+
* @author Shane Rozen-Levy ([email protected])
6+
* @brief Provides LCM subscriber object for subscribing to one or multiple LCM
7+
* channels.
8+
* @date 7/7/22
9+
*
10+
* @copyright Copyright 2022 The Trustees of the University of Pennsylvania. All
11+
* rights reserved. BSD 3-Clause License
12+
*
13+
*/
514

615
#pragma once
16+
17+
#include <map>
18+
#include <mutex>
719
#include <string>
820
#include "lcm/lcm-cpp.hpp"
921
#include "real_time_tools/thread.hpp"
1022
#include "kodlab_mjbots_sdk/abstract_realtime_object.h"
23+
#include "kodlab_mjbots_sdk/lcm_message_handler.h"
24+
#include "kodlab_mjbots_sdk/log.h"
1125

1226
namespace kodlab {
13-
/*!
14-
* @brief An template class for an lcm subscriber. Subscribes to just one msg of type msg_type.
15-
* @tparam MessageClass the lcm msg type
27+
28+
/**
29+
* @brief LCM subscriber capable of subscribing to multiple channels
30+
* @warning Running multiple `LcmSubscriber` objects on a single CPU can result
31+
* in concurrency issues. The authors do not endorse this usage, do so at your
32+
* own risk.
33+
* @todo Move implementation to source file once `CTRL_C_DETECTED` global
34+
* variable made available to TU.
1635
*/
17-
template<class MessageClass>
1836
class LcmSubscriber : public AbstractRealtimeObject {
1937
public:
38+
2039
/*!
21-
* @brief constructor for lcm subscriber
22-
* @param realtime_priority the realtime priority, max is 99
23-
* @param cpu the cpu for this process
24-
* @param channel_name the string for the channel name
40+
* @brief Constructor an lcm subscriber
41+
* @param realtime_priority realtime priority in range [1, 99]
42+
* @param cpu cpu for this process
2543
*/
26-
LcmSubscriber(int realtime_priority, int cpu, std::string channel_name);
44+
LcmSubscriber(int realtime_priority, int cpu);
2745

28-
std::mutex mutex_; /// Mutex for if m_data
29-
bool new_message_ =
30-
false; /// True if there is new data, false if data is old, used to prevent user for checking too often
31-
MessageClass data_; /// A copy of the most recent lcm data
32-
protected:
33-
/*!
34-
* @brief callback function when msg is received. Copies the message to m_data
35-
* @param rbuf unknown, but not used
36-
* @param chan channel name
37-
* @param msg ptr to the incoming message data
46+
/**
47+
* @brief Initialize the LCM subscriber thread.
3848
*/
39-
void HandleMsg(const lcm::ReceiveBuffer *rbuf,
40-
const std::string &chan,
41-
const MessageClass *msg);
49+
void Init();
4250

43-
/*!
44-
* @brief subscribes to the lcm channel using handle message and handles ctrl c detection
51+
/**
52+
* @brief Add a new LCM channel to the the LCM object's subscriptions
53+
* @tparam Message LCM message type for channel
54+
* @param channel_name channel name
55+
* @param handler `LcmMessageHandler`-inherited message handler object
56+
* @return generated `lcm::Subscription` object
4557
*/
46-
void Run() override;
58+
template<class Message>
59+
lcm::Subscription *AddSubscription(std::string channel_name,
60+
LcmMessageHandler<Message> &handler) {
61+
auto sub = lcm_.subscribe(channel_name,
62+
&LcmMessageHandler<Message>::HandleMessage,
63+
&handler);
64+
subs_.emplace(channel_name, sub);
65+
return sub;
66+
}
67+
68+
/**
69+
* @brief Remove a subscription by channel name
70+
* @note Warns and continues if channel does not exist.
71+
* @param channel_name channel name of subscription to be removed
72+
* @return 0 if unsubscribe successful, -1 if `channel_name` is not a valid
73+
* subscription
74+
*/
75+
int RemoveSubscription(const std::string &channel_name);
4776

48-
std::string channel_name_;
77+
/**
78+
* @brief Accessor for `lcm::Subscription` objects
79+
* @param channel_name channel name
80+
* @return `lcm::Subscription` object on corresponding channel if subscription
81+
* exists, `nullptr` otherwise
82+
*/
83+
[[nodiscard]] const lcm::Subscription *get_subscription(const std::string &channel_name) const;
84+
85+
private:
86+
87+
/**
88+
* @brief LCM object
89+
*/
4990
lcm::LCM lcm_;
91+
92+
/**
93+
* @brief Map of channel names to corresponding `lcm::Subscription` objects
94+
*/
95+
std::map<std::string, lcm::Subscription *> subs_;
96+
97+
/**
98+
* @brief Waits for LCM messages and exits when ctrl+c detected
99+
*/
100+
void Run() override;
101+
50102
};
51103

52-
/************************************Implementation********************************************************************/
53-
template<class msg_type>
54-
void LcmSubscriber<msg_type>::Run() {
55-
lcm_.subscribe(channel_name_, &LcmSubscriber::HandleMsg, this);
56-
mutex_.unlock(); // Ensures mutex is unlocked
57-
std::cout << "Subscribing to " << channel_name_ << std::endl;
58-
while (!CTRL_C_DETECTED) {
59-
lcm_.handleTimeout(1000);
60-
}
61-
}
62104

63-
template<class msg_type>
64-
LcmSubscriber<msg_type>::LcmSubscriber(int realtime_priority, int cpu, std::string channel_name):
65-
channel_name_(channel_name) {
105+
////////////////////////////////////////////////////////////////////////////////
106+
// Implementation //
107+
////////////////////////////////////////////////////////////////////////////////
108+
109+
LcmSubscriber::LcmSubscriber(int realtime_priority, int cpu) {
66110
cpu_ = cpu;
67111
realtime_priority_ = realtime_priority;
68-
if (!channel_name_.empty())
69-
Start();
70112
}
71113

72-
template<class msg_type>
73-
void LcmSubscriber<msg_type>::HandleMsg(const lcm::ReceiveBuffer *rbuf,
74-
const std::string &chan,
75-
const msg_type *msg) {
76-
// Lock mutex
77-
mutex_.lock();
78-
// Copy data
79-
data_ = *msg;
80-
// Let user know new message
81-
new_message_ = true;
82-
// Unlock mutex
83-
mutex_.unlock();
114+
void LcmSubscriber::Init() {
115+
Start();
84116
}
85-
} // namespace kodlab
117+
118+
int LcmSubscriber::RemoveSubscription(const std::string &channel_name) {
119+
auto it = subs_.find(channel_name);
120+
if (it != subs_.end()) {
121+
int success = lcm_.unsubscribe(subs_[channel_name]);
122+
subs_.erase(it);
123+
return success;
124+
} else {
125+
LOG_WARN("Channel \"%s\" is not in subscription list.",
126+
channel_name.c_str());
127+
}
128+
return -1;
129+
}
130+
131+
[[nodiscard]] const lcm::Subscription *LcmSubscriber::get_subscription(const std::string &channel_name) const {
132+
if (subs_.count(channel_name) == 1) {
133+
return subs_.at(channel_name);
134+
} else {
135+
LOG_ERROR("Channel \"%s\" is not in subscription list.",
136+
channel_name.c_str());
137+
return nullptr;
138+
}
139+
}
140+
141+
void LcmSubscriber::Run() {
142+
while (!CTRL_C_DETECTED) {
143+
lcm_.handleTimeout(1000);
144+
}
145+
}
146+
147+
} // kodlab
148+

0 commit comments

Comments
 (0)