PTUNode.cpp
Go to the documentation of this file.
1 #include "driver/PTUNode.h"
2 
3 
4 PTUNode::PTUNode(ros::NodeHandle& node_handle):node_handle(node_handle) {
5  std::string port;
6  int baud;
7  bool speed_control;
8  bool mock_PTU = false;
9  //goal_not_reached = false;
10  node_handle.getParam("port", port);
11  node_handle.getParam("baud", baud);
12  node_handle.getParam("speed_control", speed_control);
13  node_handle.getParam("mockPTU", mock_PTU);
14  ptu = NULL;
15  if (mock_PTU) {
16  ptu = new asr_flir_ptu_driver::PTUDriverMock(port.c_str(), baud, speed_control);
17  }
18  else {
19  bool no_device_found = true;
20  while(no_device_found) {
21  no_device_found = false;
22  try {
23  ptu = new asr_flir_ptu_driver::PTUDriver(port.c_str(), baud, speed_control);
24  }
25  catch(std::exception &e) {
26  ROS_ERROR("NO DEVICE FOUND CONNECTED TO SERIAL PORT %s", port.c_str());
27  no_device_found = true;
28  sleep(5);
29  }
30  }
31  }
32 
33  if (ptu->isConnected()) {
34  ROS_INFO("PTU is connected");
35  } else {
36  ROS_ERROR("Couldn't connect to PTU");
37  exit(1);
38  }
39 
40  setSettings();
41 
42  ROS_INFO("PTU is fully initialized.");
43 
44  joint_sub = node_handle.subscribe<asr_flir_ptu_driver::State>(topic_state_command, 1, &PTUNode::setState, this);
45  joint_pub = node_handle.advertise<asr_flir_ptu_driver::State>(ptu_topic_state, 1);
46 
47  //Required for some external packages, not asr_flir_ptu_controller (maybe fix there if time left, they expect the old sensor_msgs::JointState instead of asr_flir_ptu_driver::State)
48  joint_pub_old = node_handle.advertise<sensor_msgs::JointState>(topic_state, 1);
49 
50  //name is a placeholder for testing
54  //goal_fulfilled_service = node_handle.advertiseService("/goal_fulfilled", &PTUNode::goalFulfilled, this);
55 
56  //Required for some external packages (not asr_flir_ptu_driver). If time left, check if really necessary in the other packages.
58 
59 
62 
63 }
64 
66  if (ptu != NULL && ptu->isConnected()) {
67  ptu->setAbsoluteAngleSpeeds(0.0, 0.0);
68  }
69 }
70 
71 
72 void PTUNode::setState(const asr_flir_ptu_driver::State::ConstPtr& msg) {
73  ROS_INFO("STATE RECEIVED");
74  ROS_DEBUG("SETSTATE REQUEST - START");
75  double pan = msg->state.position[0];
76  double tilt = msg->state.position[1];
77  double pan_speed = msg->state.velocity[0];
78  double tilt_speed = msg->state.velocity[1];
79  seq_num = msg->seq_num;
80  ROS_DEBUG_STREAM("Node received state_cmd message: (p,t)=(" << pan << "," << tilt << ") and (v_p,v_t)=(" << pan_speed << "," << tilt_speed << ").");
81 
82  if (ptu->isInSpeedControlMode()) {
83  ROS_DEBUG("PTU is in SpeedcontrolMode");
84  ptu->setAbsoluteAngleSpeeds(pan_speed, tilt_speed);
85  } else {
86  ROS_DEBUG("PTU is NOT in SpeedcontrolMode");
87  ptu->setAbsoluteAngles(pan, tilt, msg->no_check_forbidden_area);
88  //goal_not_reached = true;
89  node_handle.setParam("reached_desired_position", false);
90 
91  }
92  ROS_DEBUG("SETSTATE REQUEST - END");
93 
94 }
95 
96 bool PTUNode::validatePanTilt(asr_flir_ptu_driver::Validate::Request &req, asr_flir_ptu_driver::Validate::Response &res)
97 {
98  double pan = req.pan;
99  double tilt = req.tilt;
100  double margin = req.margin;
101  //Reicht das, brauch ich die normale Überprüfung nicht?
102  if(ptu->setValuesOutOfLimitsButWithinMarginToLimit(&pan, &tilt, margin)) {
103  if(ptu->isInForbiddenArea(pan, tilt)) {
104  res.is_valid = false;
105  ROS_DEBUG("FORBIDDEN");
106  }
107  else {
108  res.is_valid = true;
109  ROS_DEBUG("VALID");
110  }
111  }
112  else {
113  res.is_valid = false;
114  ROS_DEBUG("OUT OF BOUNDS");
115  }
116  res.new_pan = pan;
117  res.new_tilt = tilt;
118  return true;
119 }
120 
121 bool PTUNode::predict(asr_flir_ptu_driver::Predict::Request &req, asr_flir_ptu_driver::Predict::Response &res) {
122  std::vector<double> legit_end_point = ptu->determineLegitEndPoint(req.pan, req.tilt);
123  res.new_pan = legit_end_point[0];
124  res.new_tilt = legit_end_point[1];
125  return true;
126 }
127 
128 
129 bool PTUNode::emptyAlive(std_srvs::Empty::Request &req, std_srvs::Empty::Response &res)
130 {
131  //Empty because service is meant to be used with a steady connection (see http://wiki.ros.org/roscpp/Overview/Services point 2.1) to check if PTUNode is working/running
132  return true;
133 }
134 
135 
136 bool PTUNode::updateSettings(std_srvs::Empty::Request&, std_srvs::Empty::Response&) {
137  return setSettings();
138 }
139 
140 bool PTUNode::getRange(asr_flir_ptu_driver::Range::Request &req, asr_flir_ptu_driver::Range::Response &res)
141 {
142  res.pan_min_angle = ptu->getLimitAngle('p', 'l');
143  res.pan_max_angle = ptu->getLimitAngle('p', 'u');
144  res.tilt_min_angle = ptu->getLimitAngle('t', 'l');
145  res.tilt_max_angle = ptu->getLimitAngle('t', 'u');
146  std::vector<double> forbidden_pan_min, forbidden_pan_max, forbidden_tilt_min, forbidden_tilt_max;
147  node_handle.getParam("forbidden_pan_min", forbidden_pan_min);
148  node_handle.getParam("forbidden_pan_max", forbidden_pan_max);
149  node_handle.getParam("forbidden_tilt_min", forbidden_tilt_min);
150  node_handle.getParam("forbidden_tilt_max", forbidden_tilt_max);
151  res.forbidden_pan_min = forbidden_pan_min;
152  res.forbidden_pan_max = forbidden_pan_max;
153  res.forbidden_tilt_min = forbidden_tilt_min;
154  res.forbidden_tilt_max = forbidden_tilt_max;
155  return true;
156 
157 }
158 
160  int pan_base, pan_speed, pan_upper, pan_accel;
161  int tilt_base, tilt_speed, tilt_upper, tilt_accel;
162  int pan_hold, pan_move;
163  int tilt_hold, tilt_move;
164  double pan_min, pan_max;
165  double tilt_min, tilt_max;
166  bool speed_control;
167 
168  double computation_tolerance;
169  int distance_factor;
170 
171 
172  //get all ptu settings
173  node_handle.getParam("speed_control", speed_control);
174  node_handle.getParam("pan_min_angle", pan_min);
175  node_handle.getParam("pan_max_angle", pan_max);
176  node_handle.getParam("pan_base_speed", pan_base);
177  node_handle.getParam("pan_target_speed", pan_speed);
178  node_handle.getParam("pan_upper_speed", pan_upper);
179  node_handle.getParam("pan_accel", pan_accel);
180  node_handle.getParam("pan_hold", pan_hold);
181  node_handle.getParam("pan_move", pan_move);
182  node_handle.getParam("tilt_min_angle", tilt_min);
183  node_handle.getParam("tilt_max_angle", tilt_max);
184  node_handle.getParam("tilt_base_speed", tilt_base);
185  node_handle.getParam("tilt_target_speed", tilt_speed);
186  node_handle.getParam("tilt_upper_speed", tilt_upper);
187  node_handle.getParam("tilt_accel", tilt_accel);
188  node_handle.getParam("tilt_hold", tilt_hold);
189  node_handle.getParam("tilt_move", tilt_move);
190  node_handle.getParam("computation_tolerance", computation_tolerance);
191  node_handle.getParam("distance_factor", distance_factor);
192 
193  std::vector<double> forbidden_pan_min;
194  std::vector<double> forbidden_pan_max;
195  std::vector<double> forbidden_tilt_min;
196  std::vector<double> forbidden_tilt_max;
197  node_handle.getParam("forbidden_pan_min", forbidden_pan_min);
198  node_handle.getParam("forbidden_pan_max", forbidden_pan_max);
199  node_handle.getParam("forbidden_tilt_min", forbidden_tilt_min);
200  node_handle.getParam("forbidden_tilt_max", forbidden_tilt_max);
201 
202  std::vector< std::map< std::string, double> > forbidden_areas;
203  for (unsigned int i = 0; i < forbidden_pan_min.size(); i++)
204  {
205  std::map< std::string, double> area;
206  area["pan_min"] = forbidden_pan_min.at(i);
207  area["pan_max"] = forbidden_pan_max.at(i);
208  area["tilt_min"] = forbidden_tilt_min.at(i);
209  area["tilt_max"] = forbidden_tilt_max.at(i);
210  if (forbidden_pan_min.at(i) < forbidden_pan_max.at(i) &&
211  forbidden_tilt_min.at(i) < forbidden_tilt_max.at(i))
212  forbidden_areas.push_back(area);
213  ROS_DEBUG("forbidden area: %d", i+1);
214  ROS_DEBUG("forbidden_pan_min:%f, forbidden_pan_min:%f", forbidden_pan_min.at(i), forbidden_pan_max.at(i));
215  ROS_DEBUG("forbidden_tilt_min:%f, forbidden_tilt_max:%f", forbidden_tilt_min.at(i), forbidden_tilt_max.at(i));
216 
217  }
218 
219  ROS_DEBUG("double minPan:%f, double maxPan:%f", pan_min, pan_max);
220  ROS_DEBUG("double tiltMin:%f, double tiltMax:%f", tilt_min, tilt_max);
221 
222 
223  // configurate PTU driver with parameters loaded via node handle
224  ptu->setSettings(pan_base, tilt_base, pan_speed, tilt_speed, pan_upper, tilt_upper, pan_accel,
225  tilt_accel, pan_hold, tilt_hold, pan_move, tilt_move);
226  ptu->setForbiddenAreas(forbidden_areas);
227  ptu->setLimitAngles(pan_min, pan_max, tilt_min, tilt_max);
228  ptu->setComputationTolerance(computation_tolerance);
229  ptu->setDistanceFactor(distance_factor);
230 
231  node_handle.setParam("pan_min_angle", (int) ptu->getLimitAngle('p','l'));
232  node_handle.setParam("pan_max_angle", (int) ptu->getLimitAngle('p','u'));
233  node_handle.setParam("pan_base_speed", pan_base);
234  node_handle.setParam("pan_target_speed", pan_speed);
235  node_handle.setParam("pan_upper_speed", pan_upper);
236  node_handle.setParam("pan_accel", pan_accel);
237  node_handle.setParam("pan_hold", pan_hold);
238  node_handle.setParam("pan_move", pan_move);
239  node_handle.setParam("tilt_min_angle", (int) ptu->getLimitAngle('t','l'));
240  node_handle.setParam("tilt_max_angle", (int) ptu->getLimitAngle('t','u'));
241  node_handle.setParam("tilt_base_speed", tilt_base);
242  node_handle.setParam("tilt_target_speed", tilt_speed);
243  node_handle.setParam("tilt_upper_speed", tilt_upper);
244  node_handle.setParam("tilt_accel", tilt_accel);
245  node_handle.setParam("tilt_hold", tilt_hold);
246  node_handle.setParam("tilt_move", tilt_move);
247 
248  // load names of coordinate systems
249  node_handle.getParam("ptu_pan_frame", ptu_pan_frame);
250  node_handle.getParam("ptu_pan_frame_rotated", ptu_pan_frame_rotated);
251  node_handle.getParam("ptu_tilt_frame", ptu_tilt_frame);
252  node_handle.getParam("ptu_tilt_frame_rotated", ptu_tilt_frame_rotated);
253 
254  // load names of ROS topics
255  node_handle.getParam("topicStateCommand", topic_state_command);
256  node_handle.getParam("topicState", topic_state);
257  node_handle.getParam("ptuTopicState", ptu_topic_state);
258 
259  ROS_DEBUG("topicStateCommand: %s", topic_state_command.c_str());
260  ROS_DEBUG("topicState: %s", topic_state.c_str());
261 
262  // load names of ROS services
263  node_handle.getParam("serviceSettingsUpdate", service_settings_update);
264  node_handle.getParam("serviceSpeedControlUpdate", service_speed_control_update);
265  node_handle.getParam("serviceRange", service_range);
266  node_handle.getParam("serviceValidation", service_validation);
267  node_handle.getParam("serviceAlive", service_alive);
268  node_handle.getParam("servicePathPrediction", service_path_prediction);
269 
270  ROS_DEBUG("serviceSettingsUpdate: %s", service_settings_update.c_str());
271  ROS_DEBUG("serviceSpeedControlUpdate: %s", service_speed_control_update.c_str());
272  ROS_DEBUG("serviceRange: %s", service_range.c_str());
273 
274  //add the namespace of the asr_flir_ptu_driver to the list of ptu namespaces. This is necessary to 'find' the PTU with the gui
275  if (node_handle.hasParam("ptu_namespaces"))
276  {
277  ROS_INFO("Set namespace");
278  node_handle.setParam("/ptu_namespaces",node_handle.getNamespace());
279  }
280 
281  return true;
282 }
283 
284 bool PTUNode::updateSpeedControl(std_srvs::Empty::Request&, std_srvs::Empty::Response&) {
285  return setSpeedControl();
286 }
287 
289  int pan_speed, tilt_speed;
290  bool speed_control;
291  node_handle.getParam("speed_control", speed_control);
292  node_handle.getParam("pan_target_speed", pan_speed);
293  node_handle.getParam("tilt_target_speed", tilt_speed);
294 
295  //not critical, but needs more testing: does PTU keep current speed when it's switched to position mode?
296  //this code assumes it does
297 
298  if (ptu->isInSpeedControlMode() & !speed_control) {
299  ptu->setAbsoluteAngleSpeeds(0.0, 0.0);
300  ptu->setSpeedControlMode(speed_control);
301  ptu->setAbsoluteAngleSpeeds((signed short)pan_speed, (signed short)tilt_speed);
302  } else if (!ptu->isInSpeedControlMode() & speed_control) {
303  ptu->setSpeedControlMode(speed_control);
304  }
305  return true;
306 }
307 
308 //check to see if PTU is still connected
309 bool PTUNode::ok() {
310  return ros::ok() && ptu->isConnected();
311 }
312 
314  //set stopped and whether or not the ptu is in speed control mode
315  //PROBLEM HERE: When new state arrives it can have wrong value on the first publishing when goal that was executed before succeeded
316 
317  //if(goal_not_reached || ptu->isInSpeedControlMode()) {
318  bool done = ptu->hasHaltedAndReachedGoal();
319  node_handle.setParam("reached_desired_position", done);
320  //if(done) goal_not_reached = false;
321 
322  //update joint state
323  sensor_msgs::JointState state;
324  asr_flir_ptu_driver::State msg;
325  state.header.stamp = ros::Time::now();
326  state.header.frame_id = (ptu->isInSpeedControlMode())? "speed_control" : "position_control"; //mostly for debugging
327  state.name.resize(2);
328  state.position.resize(2);
329  state.velocity.resize(2);
330  state.name[0] = "ptu_pan";
331  state.position[0] = ptu->getCurrentAngle(PAN);
332  state.velocity[0] = ptu->getAngleSpeed(PAN);
333  state.name[1] = "ptu_tilt";
334  state.position[1] = ptu->getCurrentAngle(TILT);
335  state.velocity[1] = ptu->getAngleSpeed(TILT);
336  msg.state = state;
337  msg.seq_num = seq_num;
338  msg.finished = done;
339  joint_pub.publish(msg);
340  joint_pub_old.publish(state);
341  //}
342 
343  // publish updated PTU coordinate system
346  tb_pan.sendTransform(panStamped);
347 
350  tb_tilt.sendTransform(tiltStamped);
351 
352  ros::spinOnce();
353 }
354 
355 int main(int argc, char **argv) {
356  ros::init(argc, argv, "ptu_driver");
357  ros::NodeHandle n("~");
358  PTUNode* node = new PTUNode(n);
359 
360  double update_rate = 100;
361  n.getParam("update_rate", update_rate);
362  ros::Rate loop_rate(update_rate);
363  while (node->ok()) {
364  node->spinOnce();
365  loop_rate.sleep();
366  }
367  delete node;
368 
369 }
virtual void setSpeedControlMode(bool speed_control_mode)
setSpeedControlMode Method to set and disable pure speed control mode
Definition: PTUDriver.cpp:523
int seq_num
Definition: PTUNode.h:69
virtual bool isConnected()
isConnected Method to check if ptu is connected.
Definition: PTUDriver.cpp:69
virtual std::vector< double > determineLegitEndPoint(double end_point_pan_candidate, double end_point_tilt_candidate)
determineLegitEndPoint Method to determine an end point which is not inside a forbidden area using pa...
Definition: PTUDriver.cpp:1026
std::string ptu_pan_frame
Definition: PTUNode.h:78
bool validatePanTilt(asr_flir_ptu_driver::Validate::Request &req, asr_flir_ptu_driver::Validate::Response &res)
Definition: PTUNode.cpp:96
void publish(const boost::shared_ptr< M > &message) const
virtual void setComputationTolerance(double computation_tolerance)
setComputationTolerance Sets the tolerance value for errors due to the imprecise nature of float...
Definition: PTUDriver.cpp:78
std::string ptu_tilt_frame_rotated
Definition: PTUNode.h:81
virtual void setDistanceFactor(long distance_factor)
setDistanceFactor Method to set the factor which determines how much samples are going to be taken fo...
Definition: PTUDriver.cpp:82
std::string topic_state
Definition: PTUNode.h:85
bool updateSpeedControl(std_srvs::Empty::Request &, std_srvs::Empty::Response &)
Definition: PTUNode.cpp:284
~PTUNode()
Definition: PTUNode.cpp:65
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
virtual bool setAbsoluteAngles(double pan_angle, double tilt_angle, bool no_forbidden_area_check)
setAbsoluteAngles Method to set pan and tilt angle for the PTU
Definition: PTUDriver.cpp:453
virtual bool isInSpeedControlMode()
isInSpeedControlMode Method to determine if ptu is in pure speed control mode
Definition: PTUDriver.cpp:532
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
int main(int argc, char **argv)
Definition: PTUNode.cpp:355
ros::ServiceServer alive_service
Definition: PTUNode.h:63
virtual bool setValuesOutOfLimitsButWithinMarginToLimit(double *pan, double *tilt, double margin)
setValuesOutOfLimitsButWithinMarginToLimit This method is used to set pan/tilt values that are slighl...
Definition: PTUDriver.cpp:400
bool setSettings()
Definition: PTUNode.cpp:159
virtual long getLimitAngle(char pan_or_tilt, char upper_or_lower)
!!!!!!!!!Müssen nachfolgende public sein? Nach erstellen von mock überprüfen!!!!!!!!!!! ...
Definition: PTUDriver.cpp:278
virtual void setSettings(int pan_base, int tilt_base, int pan_speed, int tilt_speed, int pan_upper, int tilt_upper, int pan_accel, int tilt_accel, int pan_hold, int tilt_hold, int pan_move, int tilt_move)
setSettings Method to configure various settings of the ptu
Definition: PTUDriver.cpp:87
ServiceServer advertiseService(const std::string &service, bool(T::*srv_func)(MReq &, MRes &), T *obj)
ros::Publisher joint_pub
Definition: PTUNode.h:60
void setState(const asr_flir_ptu_driver::State::ConstPtr &msg)
Definition: PTUNode.cpp:72
virtual void setLimitAngles(double pan_min, double pan_max, double tilt_min, double tilt_max)
setSettings Method to configure limits for pan and tilt
Definition: PTUDriver.cpp:487
ros::Publisher joint_pub_old
Definition: PTUNode.h:61
PTUNode(ros::NodeHandle &n)
PTUNode Constructor for PTUNode class.
Definition: PTUNode.cpp:4
std::string service_path_prediction
Definition: PTUNode.h:93
bool predict(asr_flir_ptu_driver::Predict::Request &req, asr_flir_ptu_driver::Predict::Response &res)
Definition: PTUNode.cpp:121
#define ROS_INFO(...)
virtual void setForbiddenAreas(std::vector< std::map< std::string, double > > forbidden_areas)
setForbiddenAreas Method to set all forbidden areas for the PTU. Former forbidden areas will be disca...
Definition: PTUDriver.cpp:308
std::string ptu_pan_frame_rotated
Definition: PTUNode.h:79
bool ok()
ok Method to check if the PTUNode is still working
Definition: PTUNode.cpp:309
std::string service_range
Definition: PTUNode.h:90
ROSCPP_DECL bool ok()
void sendTransform(const StampedTransform &transform)
const std::string & getNamespace() const
ros::Subscriber joint_sub
Definition: PTUNode.h:59
virtual double getAngleSpeed(char type)
getAngleSpeed Method to get the current angle speed of an axisa (deg/s)
Definition: PTUDriver.cpp:561
std::string service_validation
Definition: PTUNode.h:91
ros::ServiceServer settings_service
Definition: PTUNode.h:64
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
ros::ServiceServer validate_service
Definition: PTUNode.h:62
virtual double getCurrentAngle(char type)
getCurrentAngle Method to get the current pan/tilt angle
Definition: PTUDriver.cpp:536
std::string topic_state_command
Definition: PTUNode.h:84
#define ROS_DEBUG_STREAM(args)
bool getRange(asr_flir_ptu_driver::Range::Request &req, asr_flir_ptu_driver::Range::Response &res)
Definition: PTUNode.cpp:140
tf::TransformBroadcaster tb_pan
Definition: PTUNode.h:74
bool sleep()
virtual bool hasHaltedAndReachedGoal()
hasHaltedAndReachedGoal Method to determine if the PTU has halted and reached its goal ...
Definition: PTUDriver.cpp:642
static constexpr double DEG_TO_RAD
Definition: PTUNode.h:96
asr_flir_ptu_driver::PTUDriver * ptu
Definition: PTUNode.h:56
ros::ServiceServer path_prediction_service
Definition: PTUNode.h:66
bool getParam(const std::string &key, std::string &s) const
std::string ptu_topic_state
Definition: PTUNode.h:71
bool updateSettings(std_srvs::Empty::Request &, std_srvs::Empty::Response &)
Definition: PTUNode.cpp:136
static Time now()
std::string service_alive
Definition: PTUNode.h:92
virtual bool isInForbiddenArea(double pan_angle, double tilt_angle)
isInForbiddenArea method to determine if a pan + tilt value pair lies within a forbidden area...
Definition: PTUDriver.cpp:362
#define TILT
Definition: PTUDriver.h:26
bool hasParam(const std::string &key) const
void spinOnce()
spinOnce Method that is used to spin the PTUNode once, including ros::spinOnce(). Run this in a loop...
Definition: PTUNode.cpp:313
ROSCPP_DECL void spinOnce()
ros::NodeHandle node_handle
Definition: PTUNode.h:57
virtual void setAbsoluteAngleSpeeds(double pan_speed, double tilt_speed)
setAbsoluteAngleSpeeds Method to set desired speed for pan/tilt movement absolute in deg/s ...
Definition: PTUDriver.cpp:331
#define ROS_ERROR(...)
void setParam(const std::string &key, const XmlRpc::XmlRpcValue &v) const
bool emptyAlive(std_srvs::Empty::Request &req, std_srvs::Empty::Response &res)
Definition: PTUNode.cpp:129
std::string service_settings_update
Definition: PTUNode.h:88
ros::ServiceServer speedmode_service
Definition: PTUNode.h:65
std::string service_speed_control_update
Definition: PTUNode.h:89
bool setSpeedControl()
Definition: PTUNode.cpp:288
std::string ptu_tilt_frame
Definition: PTUNode.h:80
#define PAN
Definition: PTUDriver.h:25
tf::TransformBroadcaster tb_tilt
Definition: PTUNode.h:75
ros::ServiceServer range_service
Definition: PTUNode.h:67
#define ROS_DEBUG(...)


asr_flir_ptu_driver
Author(s): Valerij Wittenbeck, Joachim Gehrung, Pascal Meißner, Patrick Schlosser
autogenerated on Mon Dec 2 2019 03:15:17