$darkmode
hmi_contact.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2020 Robert Bosch GmbH
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * SPDX-License-Identifier: Apache-2.0
17  */
69 #pragma once
70 
71 #include <cmath> // for fmod
72 #include <map> // for map<>
73 #include <memory> // for shared_ptr<>
74 #include <string> // for string
75 #include <type_traits> // for enable_if<>
76 #include <utility> // for make_pair<>
77 
78 #include <boost/algorithm/string.hpp> // for split, is_any_of
79 
80 #include <cloe/core.hpp> // for Confable, Json, Schema, Duration
81 #include <cloe/trigger.hpp> // for Trigger, Action, ActionFactory
82 
83 namespace cloe {
84 namespace utility {
85 
97 template <typename D = Duration>
98 class Contact {
99  public:
100  explicit Contact(bool active) : active_(active) {}
101  virtual ~Contact() = default;
102 
109  void update(D time, bool down) {
110  // Skip if we are not releasing and we are also not pushing down.
111  // This reduces the nominal case (false | false) to a single if instruction.
112  if (active_ | down) {
113  if (down) {
114  contact_down(time);
115  } else {
116  contact_up(time);
117  }
118  }
119  }
120 
121  bool has_contact() const { return active_; }
122 
123  protected:
130  virtual void contact_down(D time) = 0;
131 
138  virtual void contact_up(D time) = 0;
139 
140  // Dynamic state
141  bool active_{false};
142 };
143 
151 template <typename D = Duration>
152 class ContactMap : public Confable {
153  struct Button {
154  std::shared_ptr<Contact<D>> contact;
155  bool state;
156  };
157 
158  std::map<std::string, Button> buttons_;
159  schema::Struct schema_;
160 
161  public:
165  void add(const std::string& key, std::shared_ptr<Contact<D>> c) {
166  if (buttons_.count(key)) {
167  throw std::logic_error("HMI contact '" + key + "' already exists.");
168  }
169  buttons_.emplace(std::make_pair(key, Button{c, c->has_contact()}));
170  schema_.set_property(key, Schema(&(buttons_[key].state), "push " + key + " HMI contact"));
171  }
172 
173  void add_new(const std::string& key, Contact<D>* c) { add(key, std::shared_ptr<Contact<D>>{c}); }
174 
180  void update(D time) {
181  for (auto& elem : buttons_) {
182  auto& b = elem.second;
183  b.contact->update(time, b.state);
184  }
185  }
186 
187  Schema schema() { return schema_; }
188 
189  void to_json(Json& j) const override {
190  for (auto& elem : buttons_) {
191  j[elem.first] = elem.second.state;
192  }
193  }
194 
195  void from_conf(const Conf& c) override {
196  for (auto& elem : buttons_) {
197  c.try_from(elem.first, elem.second.state);
198  }
199  }
200 
201  CONFABLE_FRIENDS(ContactMap<D>)
202 };
203 
204 template <typename D = Duration>
205 class UseContact : public Action {
206  public:
207  UseContact(const std::string& name, ContactMap<D>* m, const Conf& data)
208  : Action(name), hmi_(m), data_(data) {}
209  ActionPtr clone() const override { return std::make_unique<UseContact<D>>(name(), hmi_, data_); }
210  CallbackResult operator()(const Sync&, TriggerRegistrar&) override { from_json(*data_, *hmi_); return CallbackResult::Ok; }
211 
212  protected:
213  void to_json(Json& j) const override { j = *data_; }
214 
215  private:
216  ContactMap<D>* hmi_;
217  Conf data_;
218 };
219 
220 template <typename D = Duration>
222  public:
223  using ActionType = UseContact<D>;
224  ContactFactory(ContactMap<D>* m, const std::string& name = "hmi")
225  : ActionFactory(name, "connect and disconnect button contacts"), contacts_(m) {}
226 
227  TriggerSchema schema() const override {
228  return TriggerSchema{
229  this->name(),
230  this->description(),
231  InlineSchema("comma-separated list of buttons to press", "[!]<button>[,...]", true),
232  contacts_->schema(),
233  };
234  }
235 
236  ActionPtr make(const Conf& c) const override {
237  return std::make_unique<UseContact<D>>(name(), contacts_, c);
238  }
239 
251  ActionPtr make(const std::string& s) const override {
252  // 1. Split string by comma and ensure there is a valid key in each.
253  // (Note: it is infuriating that this is no easy way to do this in C++.)
254  std::vector<std::string> fields;
255  boost::split(fields, s, boost::is_any_of(","));
256 
257  // 2. Fill JSON with fields set to correct values.
258  Json j;
259  for (const auto& f : fields) {
260  if (f.size() == 0) {
261  throw Error("empty entry in comma-separated list");
262  }
263  bool value = (f[0] == '!' ? false : true);
264  std::string key = (value ? f : f.substr(1));
265  j[key] = value;
266  }
267 
268  return make(Conf{j});
269  }
270 
271  private:
272  ContactMap<D>* contacts_;
273 };
274 
287 template <typename D = Duration>
288 class Switch : public Contact<D> {
289  public:
290  Switch(std::function<void()> set_fn, std::function<void()> unset_fn, bool active)
291  : Contact<D>(active), set_func_(set_fn), unset_func_(unset_fn) {}
292  virtual ~Switch() = default;
293 
294  protected:
295  void contact_down(D) override {
296  // If we are already active, then ignore
297  if (!this->active_) {
298  this->active_ = true;
299  set_func_();
300  }
301  }
302 
303  void contact_up(D) override {
304  assert(this->active_);
305  this->active_ = false;
306  unset_func_();
307  }
308 
309  private:
310  std::function<void()> set_func_;
311  std::function<void()> unset_func_;
312 };
313 
334 template <typename D = Duration>
335 class PushButton : public Contact<D> {
336  public:
337  explicit PushButton(std::function<void()> click_fn) : Contact<D>(false), single_func_(click_fn) {}
338  PushButton(std::function<void()> click_fn, std::function<void()> repeat_fn)
339  : Contact<D>(false), single_func_(click_fn), repeat_func_(repeat_fn) {}
340  virtual ~PushButton() = default;
341 
347  void set_delay(D delay) { delay_ = delay; }
348 
355  void set_interval(D interval) { interval_ = interval; }
356 
357  protected:
358  void contact_down(D time) override {
359  if (!this->active_) {
360  this->active_ = true;
361  last_event_ = time;
362  return;
363  } else if (!repeat_func_) {
364  // If repeat_func_ is not set, than we don't care about the time,
365  // and we will never trigger while the button is depressed.
366  return;
367  }
368 
369  if (repeated_) {
370  // We've already activated the button at least once.
371  if (time - last_event_ > interval_) {
372  repeat_func_();
373  last_event_ = time;
374  }
375  } else {
376  // We have not activated the button yet.
377  if (time - last_event_ > delay_) {
378  repeat_func_();
379  last_event_ = time;
380  repeated_ = true;
381  }
382  }
383  }
384 
385  void contact_up(D) override {
386  assert(this->active_);
387  if (!repeated_) {
388  single_func_();
389  }
390  reset();
391  }
392 
393  void reset() {
394  this->active_ = false;
395  repeated_ = false;
396  last_event_ = D::zero();
397  }
398 
399  private:
400  std::function<void()> single_func_;
401  std::function<void()> repeat_func_;
402  D delay_{std::chrono::milliseconds(500)};
403  D interval_{std::chrono::milliseconds(250)};
404 
405  // Dynamic state
406  D last_event_{D::zero()};
407  bool repeated_{false};
408 };
409 
418 namespace contact {
419 
425 template <typename N>
426 typename std::enable_if<std::is_integral<N>::value, N>::type round_step(N target, N increment) {
427  auto rem = target % increment;
428  if (rem != 0) {
429  if (increment < 0) {
430  return target - rem;
431  } else {
432  return target + (increment - rem);
433  }
434  } else {
435  return target + increment;
436  }
437 }
438 
444 template <typename N>
445 typename std::enable_if<std::is_floating_point<N>::value, N>::type round_step(N target,
446  N increment) {
447  auto rem = fmod(target, increment);
448  if (rem != 0.0) {
449  if (increment < 0) {
450  return target - rem;
451  } else {
452  return target + (increment - rem);
453  }
454  } else {
455  return target + increment;
456  }
457 }
458 
464 template <typename D = Duration>
465 Switch<D>* make_switch(bool* ptr) {
466  assert(ptr != nullptr);
467  return new Switch<D>([ptr]() { *ptr = true; }, [ptr]() { *ptr = false; }, *ptr);
468 }
469 
473 template <typename D = Duration>
475  assert(ptr != nullptr);
476  return PushButton<D>([ptr]() { *ptr = !(*ptr); });
477 }
478 
482 template <typename D = Duration, typename N>
483 PushButton<D> make_step(N* ptr, N single) {
484  assert(ptr != nullptr);
485  return PushButton<D>([ptr, single]() { *ptr += single; });
486 }
487 
492 template <typename D = Duration, typename N>
493 PushButton<D> make_step(N* ptr, N single, N multiple) {
494  assert(ptr != nullptr);
495  return PushButton<D>([ptr, single]() { *ptr += single; },
496  [ptr, multiple]() { *ptr += multiple; });
497 }
498 
503 template <typename D = Duration, typename N>
504 PushButton<D> make_round_step(N* ptr, N single) {
505  assert(ptr != nullptr);
506  assert(single != 0);
507  return PushButton<D>([ptr, single]() { *ptr = round_step(*ptr, single); });
508 }
509 
515 template <typename D = Duration, typename N>
516 PushButton<D> make_round_step(N* ptr, N single, N multiple) {
517  assert(ptr != nullptr);
518  assert(single != 0);
519  assert(multiple != 0);
520  return PushButton<D>([ptr, single]() { *ptr = round_step(*ptr, single); },
521  [ptr, multiple]() { *ptr = round_step(*ptr, multiple); });
522 }
523 
530 template <typename D = Duration, typename N>
532  assert(ptr != nullptr);
533  assert(single != 0);
534  return PushButton<D>([ptr, single]() {
535  *ptr = round_step(*ptr, single);
536  if (*ptr < 0) {
537  *ptr = 0;
538  }
539  });
540 }
541 
549 template <typename D = Duration, typename N>
550 PushButton<D> make_round_step_nonnegative(N* ptr, N single, N multiple) {
551  assert(ptr != nullptr);
552  assert(single != 0);
553  assert(multiple != 0);
554  return PushButton<D>(
555  [ptr, single]() {
556  *ptr = round_step(*ptr, single);
557  if (*ptr < 0) {
558  *ptr = 0;
559  }
560  },
561  [ptr, multiple]() {
562  *ptr = round_step(*ptr, multiple);
563  if (*ptr < 0) {
564  *ptr = 0;
565  }
566  });
567 }
568 
569 } // namespace contact
570 
571 } // namespace utility
572 } // namespace cloe
Definition: trigger.hpp:619
const std::string & description() const
Definition: entity.hpp:90
const std::string & name() const
Definition: entity.hpp:67
Definition: error.hpp:35
Definition: sync.hpp:34
Definition: trigger.hpp:290
Definition: trigger.hpp:437
Definition: hmi_contact.hpp:221
TriggerSchema schema() const override
Definition: hmi_contact.hpp:227
ActionPtr make(const Conf &c) const override
Definition: hmi_contact.hpp:236
ActionPtr make(const std::string &s) const override
Definition: hmi_contact.hpp:251
Definition: hmi_contact.hpp:152
void update(D time)
Definition: hmi_contact.hpp:180
void from_conf(const Conf &c) override
Definition: hmi_contact.hpp:195
void add(const std::string &key, std::shared_ptr< Contact< D >> c)
Definition: hmi_contact.hpp:165
Definition: hmi_contact.hpp:98
virtual void contact_down(D time)=0
virtual void contact_up(D time)=0
void update(D time, bool down)
Definition: hmi_contact.hpp:109
Definition: hmi_contact.hpp:335
void set_interval(D interval)
Definition: hmi_contact.hpp:355
void set_delay(D delay)
Definition: hmi_contact.hpp:347
Definition: hmi_contact.hpp:288
Definition: hmi_contact.hpp:205
ActionPtr clone() const override
Definition: hmi_contact.hpp:209
CallbackResult operator()(const Sync &, TriggerRegistrar &) override
Definition: hmi_contact.hpp:210
Definition: conf.hpp:76
void try_from(const std::string &key, T &val) const
Definition: conf.hpp:271
Definition: confable.hpp:43
Json to_json() const
Definition: confable.cpp:78
Definition: schema.hpp:175
Definition: struct.hpp:70
void set_property(const std::string &key, Box &&s)
Definition: struct.cpp:31
PushButton< D > make_round_step(N *ptr, N single)
Definition: hmi_contact.hpp:504
PushButton< D > make_round_step_nonnegative(N *ptr, N single)
Definition: hmi_contact.hpp:531
PushButton< D > make_toggle(bool *ptr)
Definition: hmi_contact.hpp:474
PushButton< D > make_step(N *ptr, N single)
Definition: hmi_contact.hpp:483
std::enable_if< std::is_integral< N >::value, N >::type round_step(N target, N increment)
Definition: hmi_contact.hpp:426
Switch< D > * make_switch(bool *ptr)
Definition: hmi_contact.hpp:465
Definition: trigger.hpp:87
Definition: trigger.hpp:207
CallbackResult
Definition: trigger.hpp:514