Fawkes API  Fawkes Development Version
exog_manager.cpp
1 /***************************************************************************
2  * exog_manager.cpp - Insert exog actions into Golog++
3  *
4  * Created: Mon 26 Aug 2019 CEST 15:38
5  * Copyright 2019 Victor MatarĂ© <matare@fh-aachen.de>
6  ****************************************************************************/
7 
8 /* This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Library General Public License for more details.
17  *
18  * Read the full text in the LICENSE.GPL file in the doc directory.
19  */
20 
21 #include "exog_manager.h"
22 
23 #include "execution_thread.h"
24 #include "utils.h"
25 
26 #include <core/exception.h>
27 #include <golog++/model/grounding.h>
28 #include <libs/interface/field_iterator.h>
29 
30 using namespace fawkes;
31 using namespace gologpp;
32 
33 namespace fawkes {
34 namespace gpp {
35 
36 const std::unordered_map<interface_fieldtype_t, std::string> ExogManager::iface_type_to_golog_type_{
37  {IFT_BOOL, BoolType::static_name()},
38  {IFT_BYTE, NumberType::static_name()},
39  {IFT_ENUM, SymbolType::static_name()},
40  {IFT_INT8, NumberType::static_name()},
41  {IFT_FLOAT, NumberType::static_name()},
42  {IFT_INT16, NumberType::static_name()},
43  {IFT_INT32, NumberType::static_name()},
44  {IFT_INT64, NumberType::static_name()},
45  {IFT_UINT8, NumberType::static_name()},
46  {IFT_DOUBLE, NumberType::static_name()},
47  {IFT_STRING, StringType::static_name()},
48  {IFT_UINT16, NumberType::static_name()},
49  {IFT_UINT32, NumberType::static_name()},
50  {IFT_UINT64, NumberType::static_name()}};
51 
52 /** @class ExogManager
53  * Watch/observe blackboard interfaces according to the mappings
54  * specified for exogenous actions in the agent program. The config
55  * has to specify whether some mapped backend name is supposed to be
56  * an interface ID or a pattern.
57  */
58 
59 /** @class ConfigError
60  * Thrown if the config is somehow inconsistent with the agent program.
61  */
62 
63 /** Construct a ConfigError.
64  * @param msg A message describing the error.
65  */
66 ConfigError::ConfigError(const std::string &msg) : Exception(msg.c_str())
67 {
68 }
69 
70 /** Constructor.
71  * Construct an ExogManager.
72  * @param exec_thread The Golog++ ExecutionContext to use
73  * @param config The Fawkes configuration to read config values from
74  * @param cfg_prefix The spec-specific config prefix to use
75  * @param blackboard The blackboard to use to read data from
76  * @param logger A logger instance to use for logging messages
77  */
79  Configuration * config,
80  const std::string &cfg_prefix,
81  BlackBoard * blackboard,
82  Logger * logger)
83 : exec_thread_(exec_thread), config_(config), blackboard_(blackboard), logger_(logger)
84 {
85  // Build a map to lookup all exog actions by their mapped name (i.e. the
86  // interface ID or pattern)
87  for (shared_ptr<Global> g : global_scope().globals()) {
88  shared_ptr<ExogAction> exog = std::dynamic_pointer_cast<ExogAction>(g);
89  if (exog)
90  mapped_exogs_.emplace(exog->mapping().backend_name(), exog);
91  }
92 
93  // Register an InterfaceWatcher and a PatternObserver for each
94  // watched/observed interface (pattern). These also implement the event
95  // handlers.
96  for (const string &id :
97  config->get_strings_or_defaults((cfg_prefix + "/blackboard/watch").c_str(), {})) {
98  shared_ptr<ExogAction> exog = find_mapped_exog(id);
99  watchers_.push_back(std::make_unique<InterfaceWatcher>(blackboard, id, exog, *this));
100  }
101 
102  for (const string &pattern :
103  config->get_strings_or_defaults((cfg_prefix + "/blackboard/observe").c_str(), {})) {
104  shared_ptr<ExogAction> exog = find_mapped_exog(pattern);
105  observers_.push_back(std::make_unique<PatternObserver>(blackboard, pattern, exog, *this));
106  }
107 }
108 
109 /** Get the ExogManager's thread name.
110  * @return the thread name
111  */
112 const char *
114 {
115  return "ExogManager";
116 }
117 
118 shared_ptr<ExogAction>
119 ExogManager::find_mapped_exog(const std::string &mapped_name)
120 {
121  auto map_it = mapped_exogs_.find(mapped_name);
122 
123  if (map_it == mapped_exogs_.end())
124  throw ConfigError("No exogenous action handles " + mapped_name);
125 
126  return map_it->second;
127 }
128 
129 void
130 ExogManager::exog_queue_push(shared_ptr<ExogEvent> evt)
131 {
132  exec_thread_->gologpp_context().exog_queue_push(evt);
133 }
134 
135 /** Construct an event handler.
136  * @param bb The fawkes blackboard to listen to
137  * @param exog The ExogAction to trigger on data change
138  * @param exog_mgr The ExogManager to send the ExogAction to
139  */
140 ExogManager::BlackboardEventHandler::BlackboardEventHandler(
141  BlackBoard * bb,
142  gologpp::shared_ptr<gologpp::ExogAction> exog,
143  ExogManager & exog_mgr)
144 : blackboard_(bb), target_exog_(exog), exog_manager_(exog_mgr)
145 {
146  for (const auto &pair : target_exog_->mapping().arg_mapping()) {
147  /* TODO: This is not very nice. First we have to look up the index of the
148  * mapped parameter variable and then remember to put arg values at that
149  * index when an actual event happens. This is necessary because the
150  * ExogEvent (or rather the ReferenceBase) constructor accepts only vectors
151  * with positional arguments.
152  */
153 
154  auto &var_ref = dynamic_cast<Reference<Variable> &>(*pair.second);
155 
156  // Make sure argument types match interface
157  string iface_type = extract_type_name(target_exog_->mapping().backend_name());
158  Interface *iface =
159  blackboard_->open_for_reading(iface_type.c_str(), "/gologpp_interface_analysis");
161  for (fi = iface->fields(); fi != iface->fields_end(); ++fi) {
162  if (pair.first == fi.get_name())
163  break;
164  }
165  if (fi == iface->fields_end()) {
166  throw ConfigError("Interface type " + iface_type + " does not have a field `" + pair.first
167  + "'");
168  }
169  auto it = iface_type_to_golog_type_.find(fi.get_type());
170  if (it == iface_type_to_golog_type_.end()) {
171  throw Exception("Unhandled interface field type %s", fi.get_typename());
172  }
173 
174  shared_ptr<const Type> desired_type = gologpp::global_scope().lookup_type(it->second);
175  if (!desired_type)
176  throw Exception("Type %s required for field %s is not defined in the golog++ code model",
177  it->second.c_str(),
178  pair.first.c_str());
179 
180  if (!(*desired_type <= gologpp::get_type<StringType>()
181  || *desired_type >= gologpp::get_type<StringType>())
182  && fi.get_length() > 1) {
183  desired_type = gologpp::global_scope().lookup_list_type(*desired_type);
184  }
185 
186  if (!(var_ref.type() <= *desired_type || var_ref.type() >= *desired_type)) {
187  throw ConfigError(target_exog_->name() + "'s argument " + var_ref.target()->name() + " is a "
188  + var_ref.type().name() + ", but the interface field requires "
189  + desired_type->name());
190  }
191  blackboard_->close(iface);
192 
193  auto param_it =
194  std::find(target_exog_->params().begin(), target_exog_->params().end(), var_ref.target());
195  auto param_idx = param_it - target_exog_->params().begin();
196  fields_order_.emplace(pair.first, arity_t(param_idx));
197  }
198 }
199 
200 std::string
201 ExogManager::BlackboardEventHandler::extract_type_name(const std::string &iface_uid)
202 {
203  auto idx = iface_uid.find("::");
204  if (idx == std::string::npos)
205  throw ConfigError(iface_uid + " is not an interface UID. Must be IFACE_TYPE::IFACE_ID.");
206  return iface_uid.substr(0, idx);
207 }
208 
209 std::string
210 ExogManager::BlackboardEventHandler::extract_id(const std::string &iface_uid)
211 {
212  auto idx = iface_uid.find("::");
213  if (idx == std::string::npos)
214  throw ConfigError(iface_uid + " is not an interface UID. Must be IFACE_TYPE::IFACE_ID.");
215  return iface_uid.substr(idx + 2);
216 }
217 
218 ExogManager::InterfaceWatcher::InterfaceWatcher(BlackBoard * bb,
219  const string & uid,
220  shared_ptr<ExogAction> exog,
221  ExogManager & exog_mgr)
222 : BlackboardEventHandler(bb, exog, exog_mgr),
223  BlackBoardInterfaceListener("gologpp_blackboard_manager"),
224  iface_(blackboard_->open_for_reading(extract_type_name(uid).c_str(), extract_id(uid).c_str()))
225 {
226  bbil_add_data_interface(iface_);
227  blackboard_->register_listener(this, BlackBoard::BBIL_FLAG_DATA);
228 }
229 
230 ExogManager::InterfaceWatcher::~InterfaceWatcher()
231 {
232  blackboard_->unregister_listener(this);
233  blackboard_->close(iface_);
234 }
235 
236 shared_ptr<ExogEvent>
237 ExogManager::BlackboardEventHandler::make_exog_event(Interface *iface) const
238 {
239  // clang-format off
240  // alignment of assignments just makes this unreadable
241  iface->read();
242  InterfaceFieldIterator fi = iface->fields();
243  vector<unique_ptr<Value>> args(target_exog_->arity());
244 
245  while (fi != iface->fields_end()) {
246  if (target_exog_->mapping().is_mapped(fi.get_name())) {
247  auto order_it = fields_order_.find(fi.get_name());
248 
249  if (fi.get_length() == 1 || (fi.get_length() > 1 && fi.get_type() == IFT_STRING))
250  args[order_it->second].reset(field_to_value(fi, 0));
251  else if (fi.get_length() > 1) {
252  vector<unique_ptr<Value>> list_init;
253  for (unsigned int idx = 0; idx < fi.get_length(); ++idx)
254  list_init.emplace_back(
255  field_to_value(fi, idx)
256  );
257  shared_ptr<const Type> list_type = global_scope().lookup_list_type(list_init[0]->type());
258  args[order_it->second].reset(
259  new Value(*list_type, list_init)
260  );
261  }
262  else
263  throw IllegalArgumentException("%s: Field %s has length %d and type %s, which shouldn't happen",
264  iface->uid(), fi.get_name(), fi.get_length(), fi.get_typename());
265  }
266  ++fi;
267  }
268 
269  return std::make_shared<ExogEvent>(target_exog_, std::move(args));
270  // clang-format on
271 }
272 
273 void
274 ExogManager::InterfaceWatcher::bb_interface_data_changed(Interface *iface) throw()
275 {
276  try {
277  exog_manager_.exog_queue_push(make_exog_event(iface));
278  } catch (IllegalArgumentException &e) {
279  exog_manager_.logger_->log_error(exog_manager_.name(),
280  "Error when creating exogenous event: %s, ignoring event!",
281  e.what_no_backtrace());
282  } catch (gologpp::UserError &e) {
283  exog_manager_.logger_->log_error(exog_manager_.name(),
284  "Error when creating exogenous event: %s, ignoring event!",
285  e.what());
286  }
287 }
288 
289 ExogManager::PatternObserver::PatternObserver(BlackBoard * bb,
290  const std::string & pattern,
291  shared_ptr<ExogAction> exog,
292  ExogManager & exog_mgr)
293 : BlackboardEventHandler(bb, exog, exog_mgr)
294 {
295  bbio_add_observed_create(extract_type_name(pattern).c_str(), extract_id(pattern).c_str());
296  blackboard_->register_observer(this);
297 }
298 
299 ExogManager::PatternObserver::~PatternObserver()
300 {
301  blackboard_->unregister_observer(this);
302 }
303 
304 void
305 ExogManager::PatternObserver::bb_interface_created(const char *type, const char *id) throw()
306 {
307  std::lock_guard<std::mutex> locked{handler_mutex_};
308  exog_manager_.watchers_.push_back(std::make_unique<InterfaceWatcher>(
309  blackboard_, string(type) + "::" + id, target_exog_, exog_manager_));
310 }
311 
312 } // namespace gpp
313 } // namespace fawkes
BlackBoard interface listener.
The BlackBoard abstract class.
Definition: blackboard.h:46
virtual Interface * open_for_reading(const char *interface_type, const char *identifier, const char *owner=NULL)=0
Open interface for reading.
@ BBIL_FLAG_DATA
consider data events
Definition: blackboard.h:88
virtual void register_observer(BlackBoardInterfaceObserver *observer)
Register BB interface observer.
Definition: blackboard.cpp:225
virtual void register_listener(BlackBoardInterfaceListener *listener, ListenerRegisterFlag flag=BBIL_FLAG_ALL)
Register BB event listener.
Definition: blackboard.cpp:185
virtual void close(Interface *interface)=0
Close interface.
Interface for configuration handling.
Definition: config.h:65
virtual std::vector< std::string > get_strings_or_defaults(const char *path, const std::vector< std::string > &default_val)
Get list of values from configuration which is of type string, or the given default if the path does ...
Definition: config.cpp:786
Base class for exceptions in Fawkes.
Definition: exception.h:36
virtual const char * what_no_backtrace() const
Get primary string (does not implicitly print the back trace).
Definition: exception.cpp:663
Expected parameter is missing.
Definition: software.h:80
Interface field iterator.
size_t get_length() const
Get length of current field.
interface_fieldtype_t get_type() const
Get type of current field.
const char * get_name() const
Get name of current field.
const char * get_typename() const
Get type of current field as string.
Base class for all Fawkes BlackBoard interfaces.
Definition: interface.h:79
InterfaceFieldIterator fields_end()
Invalid iterator.
Definition: interface.cpp:1207
InterfaceFieldIterator fields()
Get iterator over all fields of this interface instance.
Definition: interface.cpp:1198
const char * uid() const
Get unique identifier of interface.
Definition: interface.cpp:677
void read()
Read from BlackBoard into local copy.
Definition: interface.cpp:472
Interface for logging.
Definition: logger.h:42
Thrown if the config is somehow inconsistent with the agent program.
Definition: exog_manager.h:44
const char * name()
Get the ExogManager's thread name.
ExogManager(GologppThread *exec_thread, Configuration *, const std::string &cfg_prefix, BlackBoard *, Logger *)
Constructor.
Main golog++ thread that handles program execution, i.e.
gologpp::ExecutionContext & gologpp_context()
GologppThread::gologpp_context.
Fawkes library namespace.
@ IFT_INT8
8 bit integer field
Definition: types.h:38
@ IFT_UINT32
32 bit unsigned integer field
Definition: types.h:43
@ IFT_FLOAT
float field
Definition: types.h:46
@ IFT_BYTE
byte field, alias for uint8
Definition: types.h:49
@ IFT_UINT64
64 bit unsigned integer field
Definition: types.h:45
@ IFT_UINT16
16 bit unsigned integer field
Definition: types.h:41
@ IFT_INT32
32 bit integer field
Definition: types.h:42
@ IFT_INT64
64 bit integer field
Definition: types.h:44
@ IFT_DOUBLE
double field
Definition: types.h:47
@ IFT_INT16
16 bit integer field
Definition: types.h:40
@ IFT_STRING
string field
Definition: types.h:48
@ IFT_BOOL
boolean field
Definition: types.h:37
@ IFT_ENUM
field with interface specific enum type
Definition: types.h:50
@ IFT_UINT8
8 bit unsigned integer field
Definition: types.h:39