SMACC2
Loading...
Searching...
No Matches
cb_keyboard_twist_teleop.hpp
Go to the documentation of this file.
1// Copyright 2026 RobosoftAI Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#pragma once
16
17#include <chrono>
18#include <mutex>
19
21#include <geometry_msgs/msg/twist.hpp>
23#include <smacc2/smacc.hpp>
25
26namespace cl_keyboard
27{
28
29// Drives a Twist topic from the arrow keys (decoded by the keyboard server):
30// Up/Down = +-linear step, Left/Right = +-angular step. Each arrow sets a pure
31// motion (no combining); keyboard autorepeat keeps it alive while the key is
32// held, and a deadman zeroes it on release. While no key is fresh, the
33// configured idle twist is published instead - zero by default, or a constant
34// "synthetic operator" push for unattended missions (e.g. driving the Nav2
35// assisted_teleop collision guard without a human).
36//
37// The state machine's orthogonal must create the output topic component, e.g.
38// createComponent<CpTopicPublisher<geometry_msgs::msg::Twist>>("/cmd_vel_teleop").
40{
41public:
43 float linearStep = 0.15f, float angularStep = 0.4f,
44 geometry_msgs::msg::Twist idleTwist = geometry_msgs::msg::Twist())
45 : linearStep_(linearStep), angularStep_(angularStep), idleTwist_(idleTwist)
46 {
47 }
48
49 void onEntry() override
50 {
51 // sync behavior: onEntry runs on the state machine thread, so component
52 // resolution and signal wiring are safe here
55
56 if (keyboardListener_ != nullptr)
57 {
59 }
60 else
61 {
62 RCLCPP_ERROR(getLogger(), "[%s] CpKeyboardListener1 not found", getName().c_str());
63 }
64
65 if (twistPublisher_ == nullptr)
66 {
67 RCLCPP_ERROR(
68 getLogger(), "[%s] CpTopicPublisher<Twist> not found - create it in the orthogonal",
69 getName().c_str());
70 }
71 }
72
73 void onExit() override
74 {
75 // stop pushing: leave the topic at zero for whatever runs next
76 if (twistPublisher_ != nullptr)
77 {
78 twistPublisher_->publish(geometry_msgs::msg::Twist());
79 }
80 }
81
82 void update() override
83 {
84 if (twistPublisher_ == nullptr)
85 {
86 return;
87 }
88
89 geometry_msgs::msg::Twist out;
90 bool held;
91 {
92 std::lock_guard<std::mutex> lock(mutex_);
93 auto now = std::chrono::steady_clock::now();
94 held = lastArrowTime_ && (now - *lastArrowTime_) < deadman_;
95 out = held ? heldTwist_ : idleTwist_;
96 }
98
99 RCLCPP_INFO_THROTTLE(
100 getLogger(), *getNode()->get_clock(), 2000,
101 "[%s] publishing %s twist: linear=%.2f angular=%.2f", getName().c_str(),
102 held ? "held" : "idle", out.linear.x, out.angular.z);
103 }
104
105private:
107 {
108 std::lock_guard<std::mutex> lock(mutex_);
109 heldTwist_ = geometry_msgs::msg::Twist();
110 switch (arrow)
111 {
112 case ArrowKey::Up:
113 heldTwist_.linear.x = linearStep_;
114 break;
115 case ArrowKey::Down:
116 heldTwist_.linear.x = -linearStep_;
117 break;
118 case ArrowKey::Left:
119 heldTwist_.angular.z = angularStep_;
120 break;
121 case ArrowKey::Right:
122 heldTwist_.angular.z = -angularStep_;
123 break;
124 }
125 lastArrowTime_ = std::chrono::steady_clock::now();
126 }
127
130 nullptr;
131
134 geometry_msgs::msg::Twist idleTwist_;
135
136 std::mutex mutex_;
137 geometry_msgs::msg::Twist heldTwist_;
138 std::optional<std::chrono::steady_clock::time_point> lastArrowTime_;
139
140 // keyboard autorepeat (typically ~30 Hz after the initial delay) refreshes
141 // lastArrowTime_ while a key is held; on release the twist dies within this
142 // window. Long enough to bridge the repeat-start delay stutter is NOT
143 // possible (that delay is ~500 ms) - accept one idle blip after the first
144 // press or hold through it with the repeat stream
145 std::chrono::milliseconds deadman_{350};
146};
147
148} // namespace cl_keyboard
CbKeyboardTwistTeleop(float linearStep=0.15f, float angularStep=0.4f, geometry_msgs::msg::Twist idleTwist=geometry_msgs::msg::Twist())
std::optional< std::chrono::steady_clock::time_point > lastArrowTime_
smacc2::client_core_components::CpTopicPublisher< geometry_msgs::msg::Twist > * twistPublisher_
components::CpKeyboardListener1 * keyboardListener_
void OnArrowPress(void(T::*callback)(ArrowKey arrow), T *object)
virtual rclcpp::Logger getLogger() const
virtual rclcpp::Node::SharedPtr getNode() const
void requiresComponent(SmaccComponentType *&storage, ComponentRequirement requirementType=ComponentRequirement::SOFT)