implemnted operator connection splitter
Stefan Schuermans

Stefan Schuermans commited on 2011-12-23 13:38:06
Showing 7 changed files, with 685 additions and 0 deletions.

... ...
@@ -101,6 +101,7 @@
101 101
         <li><a href="flexipix.html">FlexiPix</a></li>
102 102
         <li><a href="loveletter.html">Loveletter</a></li>
103 103
         <li><a href="opprinter.html">Operator Connection Printer</a></li>
104
+        <li><a href="opsplitter.html">Operator Connection Splitter</a></li>
104 105
         <li><a href="output.html">Output</a></li>
105 106
         <li><a href="player.html">Player</a></li>
106 107
         <li><a href="printer.html">Printer</a></li>
... ...
@@ -0,0 +1,58 @@
1
+<html>
2
+  <head>
3
+    <title>Blinker - Operator Connection Splitter</title>
4
+  </head>
5
+  <body>
6
+    <h1>Blinker - Operator Connection Splitter</h1>
7
+    <p>
8
+      The operator connection splitter is a module to accept operator
9
+      connections and allow the operator to select one of several
10
+      modules to connect to by dialing a further number of the form
11
+      <code>*12345678#</code>.
12
+      When the called module closes the connection, the operator
13
+      is connected to the splitter again and can select the next
14
+      module to connect to.
15
+      The empty number <code>*#</code> closes the incoming connection.
16
+    </p>
17
+    <h2>Configuration</h2>
18
+    <p>
19
+      The configuration of the operator connection splitter module
20
+      with name <code>NAME</code> is located in the
21
+      <code>opsplitters/NAME</code> subdirectory.
22
+    </p>
23
+    <h3>Sound File</h3>
24
+    <p>
25
+      While an operator is connected to the splitter module (e.g. via
26
+      a phone connection), a sound file can be requested to be played.
27
+      (In case of the phone connection, this file is located on the
28
+      phone server.)
29
+      The name of the sound to request is contained in the file
30
+      <code>sound</code>.
31
+      If this file does not exists, no sound is requested to be played.
32
+    </p>
33
+    <h3>Extensions / Phone Numbers</h3>
34
+    <p>
35
+      The virtual extensions (i.e. phone numbers) that can be called
36
+      by dialing a number of the form <code>*12345678#</code>
37
+      are configured in the subdirectory <code>extensions</code>.
38
+      <br>
39
+      Each extension is configured in its own subdirectory.
40
+      E.g. the configuration for the number mentioned above
41
+      resides in the subdirectory <code>extensions/12345678</code>.
42
+    </p>
43
+    <p>
44
+      The configuration inside an extension consists of the following
45
+      settings:
46
+    </p>
47
+    <h4>Module</h4>
48
+    <p>
49
+      The file <code>module</code> contains the name of the module to
50
+      contact via an operator connection if the extension is called.
51
+      The module name consists of two parts: <code>CLASS/NAME</class>
52
+      E.g. to connect to the operator connection printer named
53
+      <code>debug</code>, the file has to contain
54
+      <code>opprinters/debug</code> as module name.
55
+    </p>
56
+  </body>
57
+</html>
58
+
... ...
@@ -0,0 +1,304 @@
1
+/* Blinker
2
+   Copyright 2011 Stefan Schuermans <stefan@blinkenarea.org>
3
+   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
4
+   a blinkenarea.org project */
5
+
6
+#include <map>
7
+#include <string>
8
+
9
+#include "Directory.h"
10
+#include "File.h"
11
+#include "ListTracker.h"
12
+#include "ListTracker_impl.h"
13
+#include "Mgrs.h"
14
+#include "Module.h"
15
+#include "NameFile.h"
16
+#include "OpConn.h"
17
+#include "OpConnIf.h"
18
+#include "OpMgr.h"
19
+#include "OpSplitter.h"
20
+#include "OpSplitterExtension.h"
21
+#include "SettingFile.h"
22
+#include "Time.h"
23
+#include "TimeCallee.h"
24
+
25
+namespace Blinker {
26
+
27
+/**
28
+ * @brief constructor
29
+ * @param[in] name module name
30
+ * @param[in] mgrs managers
31
+ * @param[in] dirBase base directory
32
+ */
33
+OpSplitter::OpSplitter(const std::string &name, Mgrs &mgrs,
34
+                         const Directory &dirBase):
35
+  Module(name, mgrs, dirBase),
36
+  m_fileSound(dirBase.getFile("sound")),
37
+  m_extListTracker(*this, dirBase.getSubdir("extensions"))
38
+{
39
+  // load extensions
40
+  m_extListTracker.init();
41
+
42
+  // open operator connection interface
43
+  m_mgrs.m_opMgr.open(m_name, this);
44
+}
45
+
46
+/// virtual destructor
47
+OpSplitter::~OpSplitter()
48
+{
49
+  // close operator connection interface
50
+  m_mgrs.m_opMgr.close(m_name);
51
+
52
+  // close all connections
53
+  while (!m_mapLocal.empty()) {
54
+    MapLocal::iterator it = m_mapLocal.begin();
55
+    it->first->close();
56
+    m_mapLocal.erase(it);
57
+  }
58
+  while (!m_mapInOut.empty()) {
59
+    MapInOut::iterator it = m_mapInOut.begin();
60
+    it->first->close();
61
+    m_mapInOut.erase(it);
62
+  }
63
+  while (!m_mapOutIn.empty()) {
64
+    MapOutIn::iterator it = m_mapOutIn.begin();
65
+    it->first->close();
66
+    m_mapOutIn.erase(it);
67
+  }
68
+
69
+  // unload extensions
70
+  m_extListTracker.clear();
71
+
72
+  // cancel time callback
73
+  m_mgrs.m_callMgr.cancelTimeCall(this);
74
+}
75
+
76
+/// check for update of configuration
77
+void OpSplitter::updateConfig()
78
+{
79
+  // sound name file was modified -> re-read sound name
80
+  if (m_fileSound.checkModified())
81
+    m_fileSound.update();
82
+
83
+  // extensions update
84
+  m_extListTracker.updateConfig();
85
+}
86
+
87
+/// callback when requested time reached
88
+void OpSplitter::timeCall()
89
+{
90
+  // send sound play request on marked local operator connection
91
+  for (MapLocal::iterator it = m_mapLocal.begin();
92
+       it != m_mapLocal.end(); ++it) {
93
+    if (it->second.m_sendPlay) {
94
+      it->second.m_sendPlay = false;
95
+      if (m_fileSound.m_valid)
96
+        it->first->sendPlay(m_fileSound.m_obj.m_str);
97
+    }
98
+  }
99
+}
100
+
101
+/**
102
+ * @brief check if accepting new operator connction is possible
103
+ * @param[in] name operator interface name
104
+ * @return if accepting new connection is possible
105
+ */
106
+bool OpSplitter::acceptNewOpConn(const std::string &name)
107
+{
108
+  return true; // TODO
109
+  (void)name; // unused
110
+}
111
+
112
+/**
113
+ * @brief new operator connection
114
+ * @param[in] name operator interface name
115
+ * @param[in] pConn operator connection object
116
+ *
117
+ * The new connection may not yet be used for sending inside this callback.
118
+ */
119
+void OpSplitter::newOpConn(const std::string &name, OpConn *pConn)
120
+{
121
+  // this is a new incoming connection -> make it a local connection
122
+  newLocal(pConn);
123
+
124
+  (void)name; // unused
125
+}
126
+
127
+/**
128
+ * @brief key command received on operator connection
129
+ * @param[in] pConn operator connection object
130
+ * @param[in] key key that was pressed
131
+ */
132
+void OpSplitter::opConnRecvKey(OpConn *pConn, char key)
133
+{
134
+  // local connection -> process
135
+  MapLocal::iterator itLocal = m_mapLocal.find(pConn);
136
+  if (itLocal != m_mapLocal.end()) {
137
+    localKey(itLocal, key);
138
+    return;
139
+  }
140
+
141
+  // incoming to outgoing connection -> forward
142
+  MapInOut::iterator itInOut = m_mapInOut.find(pConn);
143
+  if (itInOut != m_mapInOut.end()) {
144
+    itInOut->second->sendKey(key);
145
+    return;
146
+  }
147
+
148
+  // outgoing to incoming connection -> forward
149
+  MapOutIn::iterator itOutIn = m_mapOutIn.find(pConn);
150
+  if (itOutIn != m_mapOutIn.end()) {
151
+    itOutIn->second->sendKey(key);
152
+    return;
153
+  }
154
+}
155
+
156
+/**
157
+ * @brief play command received on operator connection
158
+ * @param[in] pConn operator connection object
159
+ * @param[in] sound name of sound to play
160
+ */
161
+void OpSplitter::opConnRecvPlay(OpConn *pConn, const std::string &sound)
162
+{
163
+  // play command does not make sense for local connections -> ignore
164
+
165
+  // incoming to outgoing connection -> forward
166
+  MapInOut::iterator itInOut = m_mapInOut.find(pConn);
167
+  if (itInOut != m_mapInOut.end()) {
168
+    itInOut->second->sendPlay(sound);
169
+    return;
170
+  }
171
+
172
+  // outgoing to incoming connection -> forward
173
+  MapOutIn::iterator itOutIn = m_mapOutIn.find(pConn);
174
+  if (itOutIn != m_mapOutIn.end()) {
175
+    itOutIn->second->sendPlay(sound);
176
+    return;
177
+  }
178
+}
179
+
180
+/**
181
+ * @brief operator connection is closed
182
+ * @param[in] pConn operator connection object
183
+ *
184
+ * The connection may not be used for sending any more in this callback.
185
+ */
186
+void OpSplitter::opConnClose(OpConn *pConn)
187
+{
188
+  // local connection -> forget this connection
189
+  MapLocal::iterator itLocal = m_mapLocal.find(pConn);
190
+  if (itLocal != m_mapLocal.end()) {
191
+    m_mapLocal.erase(itLocal);
192
+    return;
193
+  }
194
+
195
+  // incoming to outgoing connection -> close outgoing connection as well
196
+  MapInOut::iterator itInOut = m_mapInOut.find(pConn);
197
+  if (itInOut != m_mapInOut.end()) {
198
+    itInOut->second->close();
199
+    m_mapOutIn.erase(itInOut->second);
200
+    m_mapInOut.erase(itInOut);
201
+    return;
202
+  }
203
+
204
+  // outgoing to incoming connection -> make incoming connection local
205
+  MapOutIn::iterator itOutIn = m_mapOutIn.find(pConn);
206
+  if (itOutIn != m_mapOutIn.end()) {
207
+    newLocal(itOutIn->second);
208
+    m_mapInOut.erase(itOutIn->second);
209
+    m_mapOutIn.erase(itOutIn);
210
+    return;
211
+  }
212
+}
213
+
214
+/**
215
+ * @brief create new local connection
216
+ * @param[in] pConn connection to make local
217
+ */
218
+void OpSplitter::newLocal(OpConn *pConn)
219
+{
220
+  // add connection to local connection map
221
+  Local &local = m_mapLocal[pConn];
222
+  local.m_number.clear(); // no extension dialed so far
223
+  local.m_sendPlay = true; // send play command
224
+  m_mgrs.m_callMgr.requestTimeCall(this, Time::now());
225
+}
226
+
227
+/**
228
+ * @brief a key has been pressed for a local connection
229
+ * @param[in] itLocal local connection
230
+ * @param[in] key the key pressed
231
+ */
232
+void OpSplitter::localKey(MapLocal::iterator itLocal, char key)
233
+{
234
+  switch (key) {
235
+    // begin new extension number
236
+    case '*':
237
+      itLocal->second.m_number.clear();
238
+      break;
239
+    // add digit to extension number
240
+    case '0':
241
+    case '1':
242
+    case '2':
243
+    case '3':
244
+    case '4':
245
+    case '5':
246
+    case '6':
247
+    case '7':
248
+    case '8':
249
+    case '9':
250
+      itLocal->second.m_number += key;
251
+      break;
252
+    // close local connection / call extension
253
+    case '#':
254
+      if (itLocal->second.m_number.empty())
255
+        localClose(itLocal);
256
+      else
257
+        callExtension(itLocal);
258
+      break;
259
+  }
260
+}
261
+
262
+/**
263
+ * @brief close local connection
264
+ * @param[in] itLocal local connection
265
+ */
266
+void OpSplitter::localClose(MapLocal::iterator itLocal)
267
+{
268
+  itLocal->first->close();
269
+  m_mapLocal.erase(itLocal);
270
+}
271
+
272
+/**
273
+ * @brief call extension dialed over local connection
274
+ * @param[in] itLocal local connection
275
+ */
276
+void OpSplitter::callExtension(MapLocal::iterator itLocal)
277
+{
278
+  // lookup extension
279
+  ExtMap::iterator itExt = m_extMap.find(itLocal->second.m_number);
280
+  if (itExt == m_extMap.end()) {
281
+    // unknown extension -> ignore
282
+    itLocal->second.m_number.clear(); // begin new extension number
283
+    return;
284
+  }
285
+
286
+  // try to open outgoiong operator connection to module
287
+  OpConn *pOutConn = m_mgrs.m_opMgr.connect(itExt->second, this);
288
+  if (!pOutConn)
289
+  {
290
+    // module is not ready for connection
291
+    itLocal->second.m_number.clear(); // begin new extension number
292
+    return;
293
+  }
294
+
295
+  // establish connection forwarding
296
+  m_mapInOut[itLocal->first] = pOutConn;
297
+  m_mapOutIn[pOutConn] = itLocal->first;
298
+
299
+  // connection is no more a local one
300
+  m_mapLocal.erase(itLocal);
301
+}
302
+
303
+} // namespace Blinker
304
+
... ...
@@ -0,0 +1,165 @@
1
+/* Blinker
2
+   Copyright 2011 Stefan Schuermans <stefan@blinkenarea.org>
3
+   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
4
+   a blinkenarea.org project */
5
+
6
+#ifndef BLINKER_OPSPLITTER_H
7
+#define BLINKER_OPSPLITTER_H
8
+
9
+#include <map>
10
+#include <string>
11
+
12
+#include "Directory.h"
13
+#include "File.h"
14
+#include "ListTracker.h"
15
+#include "Mgrs.h"
16
+#include "Module.h"
17
+#include "NameFile.h"
18
+#include "OpConn.h"
19
+#include "OpConnIf.h"
20
+#include "OpReqIf.h"
21
+#include "SettingFile.h"
22
+#include "Time.h"
23
+#include "TimeCallee.h"
24
+
25
+namespace Blinker {
26
+
27
+/// operator connection splitter
28
+class OpSplitter: public Module, public OpReqIf, public TimeCallee
29
+{
30
+protected:
31
+  /// extension to be called
32
+  class Extension;
33
+
34
+  /// extension list tracker
35
+  typedef ListTracker<OpSplitter, Extension, Directory> ExtListTracker;
36
+
37
+  /// map of extensions to call (extension name -> module name)
38
+  typedef std::map<std::string, std::string> ExtMap;
39
+
40
+  /// type for locally handles connection
41
+  struct Local {
42
+    std::string m_number;   ///< extension number dialed so far
43
+    bool        m_sendPlay; ///< if to send a play request
44
+  };
45
+
46
+  /**
47
+   * @brief map of local connections,
48
+   *        contains all incoming connections as key for which no
49
+   *        outgoing connection is available
50
+   */
51
+  typedef std::map<OpConn *, Local> MapLocal;
52
+
53
+  /**
54
+   * @brief map from incoming to outgoing connections,
55
+   *        contains all incoming connectes as key for which a corresponding
56
+   *        outgoing connection is available
57
+   */
58
+  typedef std::map<OpConn *, OpConn *> MapInOut;
59
+
60
+  /// map from outgoing to incoming connections
61
+  typedef std::map<OpConn *, OpConn *> MapOutIn;
62
+
63
+public:
64
+  /**
65
+   * @brief constructor
66
+   * @param[in] name module name
67
+   * @param[in] mgrs managers
68
+   * @param[in] dirBase base directory
69
+   */
70
+  OpSplitter(const std::string &name, Mgrs &mgrs, const Directory &dirBase);
71
+
72
+  /// virtual destructor
73
+  virtual ~OpSplitter();
74
+
75
+private:
76
+  /// copy constructor disabled
77
+  OpSplitter(const OpSplitter &that);
78
+
79
+  /// assignment operator disabled
80
+  const OpSplitter & operator=(const OpSplitter &that);
81
+
82
+public:
83
+  /// check for update of configuration
84
+  virtual void updateConfig();
85
+
86
+  /// callback when requested time reached
87
+  virtual void timeCall();
88
+
89
+  /**
90
+   * @brief check if accepting new operator connction is possible
91
+   * @param[in] name operator interface name
92
+   * @return if accepting new connection is possible
93
+   */
94
+  virtual bool acceptNewOpConn(const std::string &name);
95
+
96
+  /**
97
+   * @brief new operator connection
98
+   * @param[in] name operator interface name
99
+   * @param[in] pConn operator connection object
100
+   *
101
+   * The new connection may not yet be used for sending inside this callback.
102
+   */
103
+  virtual void newOpConn(const std::string &name, OpConn *pConn);
104
+
105
+  /**
106
+   * @brief key command received on operator connection
107
+   * @param[in] pConn operator connection object
108
+   * @param[in] key key that was pressed
109
+   */
110
+  virtual void opConnRecvKey(OpConn *pConn, char key);
111
+
112
+  /**
113
+   * @brief play command received on operator connection
114
+   * @param[in] pConn operator connection object
115
+   * @param[in] sound name of sound to play
116
+   */
117
+  virtual void opConnRecvPlay(OpConn *pConn, const std::string &sound);
118
+
119
+  /**
120
+   * @brief operator connection is closed
121
+   * @param[in] pConn operator connection object
122
+   *
123
+   * The connection may not be used for sending any more in this callback.
124
+   */
125
+  virtual void opConnClose(OpConn *pConn);
126
+
127
+protected:
128
+  /**
129
+   * @brief create new local connection
130
+   * @param[in] pConn connection to make local
131
+   */
132
+  void newLocal(OpConn *pConn);
133
+
134
+  /**
135
+   * @brief a key has been pressed for a local connection
136
+   * @param[in] itLocal local connection
137
+   * @param[in] key the key pressed
138
+   */
139
+  void localKey(MapLocal::iterator itLocal, char key);
140
+
141
+  /**
142
+   * @brief close local connection
143
+   * @param[in] itLocal local connection
144
+   */
145
+  void localClose(MapLocal::iterator itLocal);
146
+
147
+  /**
148
+   * @brief call extension dialed over local connection
149
+   * @param[in] itLocal local connection
150
+   */
151
+  void callExtension(MapLocal::iterator itLocal);
152
+
153
+protected:
154
+  NameFile       m_fileSound;      ///< file containing sound name
155
+  ExtListTracker m_extListTracker; ///< extension tracker
156
+  ExtMap         m_extMap;         ///< map of extensions to call
157
+  MapLocal       m_mapLocal;       ///< localy handled connections
158
+  MapInOut       m_mapInOut;       ///< map from incoming to outgoing conn.
159
+  MapOutIn       m_mapOutIn;       ///< map from outgoing to incoming conn.
160
+}; // class OpSplitter
161
+
162
+} // namespace Blinker
163
+
164
+#endif // #ifndef BLINKER_OPSPLITTER_H
165
+
... ...
@@ -0,0 +1,88 @@
1
+/* Blinker
2
+   Copyright 2011 Stefan Schuermans <stefan@blinkenarea.org>
3
+   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
4
+   a blinkenarea.org project */
5
+
6
+#include <string>
7
+
8
+#include "Directory.h"
9
+#include "File.h"
10
+#include "Module.h"
11
+#include "NameFile.h"
12
+#include "OpSplitter.h"
13
+#include "OpSplitterExtension.h"
14
+
15
+namespace Blinker {
16
+
17
+/**
18
+ * @brief constructor
19
+ * @param[in] opSplitter owning operator connection splitter object
20
+ * @param[in] name extension name (i.e. phone number)
21
+ * @param[in] dirBase base directory
22
+ */
23
+OpSplitter::Extension::Extension(OpSplitter &opSplitter,
24
+                                 const std::string &name,
25
+                                 const Directory &dirBase):
26
+  m_opSplitter(opSplitter),
27
+  m_name(name),
28
+  m_fileModule(dirBase.getFile("module"))
29
+{
30
+  // set up
31
+  name2number(m_name, m_number);
32
+  getModule();
33
+}
34
+
35
+/// destructor
36
+OpSplitter::Extension::~Extension()
37
+{
38
+  // remove extension from extension map
39
+  if (m_addedToMap)
40
+    m_opSplitter.m_extMap.erase(m_number);
41
+}
42
+
43
+/// check for update of configuration
44
+void OpSplitter::Extension::updateConfig()
45
+{
46
+  // module file was modified -> re-get module to connect to
47
+  if (m_fileModule.checkModified())
48
+    getModule();
49
+}
50
+
51
+/// (re-)get module to connect to
52
+void OpSplitter::Extension::getModule()
53
+{
54
+  // remove old extension information from extension map
55
+  if (m_addedToMap) {
56
+    m_opSplitter.m_extMap.erase(m_number);
57
+    m_addedToMap = false;
58
+  }
59
+
60
+  // get new module name from file
61
+  m_fileModule.update();
62
+
63
+  // add new module name to extension map
64
+  if (m_fileModule.m_valid) {
65
+    if (m_opSplitter.m_extMap.find(m_number) == m_opSplitter.m_extMap.end()) {
66
+      m_opSplitter.m_extMap[m_number] = m_fileModule.m_obj.m_str;
67
+      m_addedToMap = true;
68
+    }
69
+  }
70
+}
71
+
72
+/**
73
+ * @brief convert extension name to extension number
74
+ * @param[in] name extension name
75
+ * @param[out] number extension number (name without all non-digits)
76
+ */
77
+void OpSplitter::Extension::name2number(const std::string &name,
78
+                                        std::string &number)
79
+{
80
+  number.clear();
81
+  for (std::string::const_iterator pos = name.begin();
82
+       pos != name.end(); ++pos)
83
+    if (*pos >= '0' && *pos <= '9')
84
+      number += *pos;
85
+}
86
+
87
+} // namespace Blinker
88
+
... ...
@@ -0,0 +1,67 @@
1
+/* Blinker
2
+   Copyright 2011 Stefan Schuermans <stefan@blinkenarea.org>
3
+   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
4
+   a blinkenarea.org project */
5
+
6
+#ifndef BLINKER_OPSPLITTEREXTENSION_H
7
+#define BLINKER_OPSPLITTEREXTENSION_H
8
+
9
+#include <string>
10
+
11
+#include "Directory.h"
12
+#include "Module.h"
13
+#include "NameFile.h"
14
+#include "OpSplitter.h"
15
+
16
+namespace Blinker {
17
+
18
+/// extension of operator connection splitter
19
+class OpSplitter::Extension
20
+{
21
+public:
22
+  /**
23
+   * @brief constructor
24
+   * @param[in] opSplitter owning operator connection splitter object
25
+   * @param[in] name extension name (i.e. number)
26
+   * @param[in] dirBase base directory
27
+   */
28
+  Extension(OpSplitter &opSplitter, const std::string &name,
29
+            const Directory &dirBase);
30
+
31
+  /// destructor
32
+  ~Extension();
33
+
34
+private:
35
+  /// copy constructor disabled
36
+  Extension(const Extension &that);
37
+
38
+  /// assignment operator disabled
39
+  const Extension & operator=(const Extension &that);
40
+
41
+public:
42
+  /// check for update of configuration
43
+  void updateConfig();
44
+
45
+protected:
46
+  /// (re-)get module to connect to
47
+  void getModule();
48
+
49
+  /**
50
+   * @brief convert extension name to extension number
51
+   * @param[in] name extension name
52
+   * @param[out] number extension number (name without all non-digits)
53
+   */
54
+  static void name2number(const std::string &name, std::string &number);
55
+
56
+protected:
57
+  OpSplitter  &m_opSplitter; ///< owning operator connection splitter object
58
+  std::string m_name;        ///< extension name (i.e. number)
59
+  NameFile    m_fileModule;  ///< file containing module to connect to
60
+  std::string m_number;      ///< number of extension
61
+  bool        m_addedToMap;  ///< if extension number could be added to map
62
+}; // class OpSplitter::Extension
63
+
64
+} // namespace Blinker
65
+
66
+#endif // #ifndef BLINKER_OPSPLITTEREXTENSION_H
67
+
... ...
@@ -14,6 +14,7 @@
14 14
 #include "ModuleMgr.h"
15 15
 #include "ModuleMgr_impl.h"
16 16
 #include "OpPrinter.h"
17
+#include "OpSplitter.h"
17 18
 #include "Output.h"
18 19
 #include "Player.h"
19 20
 #include "Printer.h"
... ...
@@ -39,6 +40,7 @@ void run(const std::string &dirConfig)
39 40
   MODULEMGR(FlexiPix,     flexipixes);
40 41
   MODULEMGR(Loveletter,   loveletters);
41 42
   MODULEMGR(OpPrinter,    opprinters);
43
+  MODULEMGR(OpSplitter,   opsplitters);
42 44
   MODULEMGR(Output,       outputs);
43 45
   MODULEMGR(Player,       players);
44 46
   MODULEMGR(Printer,      printers);
45 47