iMSTK
Interactive Medical Simulation Toolkit
imstkOpenHapticDeviceManager.cpp
1 /*
2 ** This file is part of the Interactive Medical Simulation Toolkit (iMSTK)
3 ** iMSTK is distributed under the Apache License, Version 2.0.
4 ** See accompanying NOTICE for details.
5 */
6 
7 #include "imstkOpenHapticDeviceManager.h"
8 #include "imstkOpenHapticDeviceClient.h"
9 #include "imstkLogger.h"
10 
11 #include <HD/hd.h>
12 
13 #include <iostream>
14 #include <iomanip>
15 
16 namespace
17 {
18 struct HDstate
19 {
20  // \todo pos are redundant?
21  HDdouble pos[3];
22  HDdouble vel[3];
23  HDdouble angularVel[3];
24  HDdouble transform[16];
25  HDint buttons;
26 };
27 } // namespace
28 
29 namespace imstk
30 {
32 {
33 public:
34 
38  static bool isFatalError(const char* message)
39  {
40  HDErrorInfo error = hdGetError();
41  if (error.errorCode == HD_SUCCESS)
42  {
43  return false;
44  }
45 
46  // The HD API maintains an error stack, so in theory there could be more than one error pending.
47  // We do head recursion to get them all in the correct order, and hope we don't overrun the stack...
48  bool anotherFatalError = isFatalError(message);
49 
50  bool isFatal = ((error.errorCode != HD_WARM_MOTORS)
51  && (error.errorCode != HD_EXCEEDED_MAX_FORCE)
52  && (error.errorCode != HD_EXCEEDED_MAX_FORCE_IMPULSE)
53  && (error.errorCode != HD_EXCEEDED_MAX_VELOCITY)
54  && (error.errorCode != HD_FORCE_ERROR));
55 
56  LOG(WARNING) << "Phantom: " << message <<
57  std::endl << " Error text: '" << hdGetErrorString(error.errorCode) << "'" << std::endl <<
58  " Error code: 0x" << std::hex << std::setw(4) << std::setfill('0') << error.errorCode <<
59  " (internal: " << std::dec << error.internalErrorCode << ")" << std::endl;
60 
61  return (isFatal || anotherFatalError);
62  }
63 
64  static HDCallbackCode HDCALLBACK hapticCallback(void* pData)
65  {
66  auto impl = static_cast<OpenHapticDeviceManagerImpl*>(pData);
67  HDstate state;
68 
69  for (int num = 0; num < impl->m_deviceClients.size(); ++num)
70  {
71  HHD handle = impl->m_handles[num];
72  OpenHapticDeviceClient* client = impl->m_deviceClients[num].get();
73 
74  if (handle == HD_BAD_HANDLE || handle == HD_INVALID_HANDLE)
75  {
76  continue;
77  }
78  Vec3d force = client->getForce();
79 
80 #ifdef IMSTK_OPENHAPTICS_DEBUG
81  if (force.hasNaN())
82  {
83  force = Vec3d::Zero();
84  LOG(WARNING) << "Force has NANs";
85  }
86 #endif
87  hdBeginFrame(handle);
88 
89  //hdMakeCurrentDevice(handle);
90  hdSetDoublev(HD_CURRENT_FORCE, force.data());
91  hdGetDoublev(HD_CURRENT_POSITION, state.pos);
92  hdGetDoublev(HD_CURRENT_VELOCITY, state.vel);
93  hdGetDoublev(HD_CURRENT_ANGULAR_VELOCITY, state.angularVel);
94  hdGetDoublev(HD_CURRENT_TRANSFORM, state.transform);
95  hdGetIntegerv(HD_CURRENT_BUTTONS, &state.buttons);
96 
97  hdEndFrame(handle);
98 
99  CHECK(!isFatalError("Error in device update"));
100 
101  // Update client data from state data
102  const Quatd orientation = Quatd((Eigen::Affine3d(Eigen::Matrix4d(state.transform))).rotation());
103  client->m_transformLock.lock();
104  // OpenHaptics is in mm, change to meters
105  client->m_position << state.pos[0] * 0.001, state.pos[1] * 0.001, state.pos[2] * 0.001;
106  client->m_velocity << state.vel[0] * 0.001, state.vel[1] * 0.001, state.vel[2] * 0.001;
107  client->m_angularVelocity << state.angularVel[0], state.angularVel[1], state.angularVel[2];
108  client->m_orientation = orientation;
109  client->m_transformLock.unlock();
110 
111  client->m_dataLock.lock();
112  for (int i = 0; i < 4; i++)
113  {
114  // If button down and not previously down
115  if ((state.buttons & (1 << i)) && !client->m_buttons[i])
116  {
117  client->m_buttons[i] = true;
118  client->m_events.push_back({ i, BUTTON_PRESSED });
119  }
120  // If button not down, and previously down
121  else if (!(state.buttons & (1 << i)) && client->m_buttons[i])
122  {
123  client->m_buttons[i] = false;
124  client->m_events.push_back({ i, BUTTON_RELEASED });
125  }
126  }
127  client->m_dataLock.unlock();
128  }
129 
130  return HD_CALLBACK_CONTINUE;
131  }
132 
133  std::shared_ptr<imstk::OpenHapticDeviceClient> makeDeviceClient(std::string name)
134  {
135  auto client = std::make_shared<imstk::OpenHapticDeviceClient>(name);
136  m_deviceClients.push_back(client);
137  return client;
138  }
139 
140  bool init()
141  {
142  for (const auto& client : m_deviceClients)
143  {
144  client->initialize();
145 
146  //flush error stack
147  HDErrorInfo errorFlush;
148  while (HD_DEVICE_ERROR(errorFlush = hdGetError())) {}
149 
150  HHD handle;
151 
152  auto name = client->getDeviceName();
153  // Initialize the device
154  if (name == "")
155  {
156  handle = hdInitDevice(HD_DEFAULT_DEVICE);
157  }
158  else
159  {
160  handle = hdInitDevice(name.c_str());
161  }
162 
163  CHECK(!isFatalError("Failed to initialize device"));
164 
165  m_handles.push_back(handle);
166 
167  hdMakeCurrentDevice(handle);
168 
169  // If initialized as default, set the name
170  if (name == "")
171  {
172  // Worth noting that in this case the name will not match the actual device name and is
173  // now only useful for scene level identification, OpenHaptics provides no mechanisms
174  // for querying device names
175 
176  HDstring str = hdGetString(HD_DEVICE_SERIAL_NUMBER);
177  client->setDeviceName("Device_" + std::string(str));
178  }
179 
180  // Enable forces
181  hdEnable(HD_FORCE_OUTPUT);
182  hdEnable(HD_FORCE_RAMPING);
183 
184  CHECK(!isFatalError("Failed to enable forces"));
185 
186  LOG(INFO) << "\"" << client->getDeviceName() << "\" successfully initialized.";
187  }
188 
189  // Start the scheduler
190  m_schedulerHandle = hdScheduleAsynchronous(hapticCallback, this, HD_MAX_SCHEDULER_PRIORITY); // Call sometime later
191  hdStartScheduler();
192  CHECK(!isFatalError("Failed to schedule callback"));
193  return true;
194  }
195 
196  void update()
197  {
198  for (const auto& client : m_deviceClients)
199  {
200  client->update();
201  }
202  }
203 
204  void uninit()
205  {
206  hdStopScheduler();
207  hdUnschedule(m_schedulerHandle);
208  for (const auto handle : m_handles)
209  {
210  hdDisableDevice(handle);
211  CHECK(!isFatalError("Failed to Disable device"));
212  }
213  }
214 
215 private:
216  HDSchedulerHandle m_schedulerHandle = 0;
217  std::vector<std::shared_ptr<imstk::OpenHapticDeviceClient>> m_deviceClients;
218  std::vector<HHD> m_handles;
219 };
220 
221 OpenHapticDeviceManager::OpenHapticDeviceManager() :
222  m_impl(new OpenHapticDeviceManagerImpl)
223 {
224  // Default a 1ms sleep to avoid over consumption of the CPU
225  setSleepDelay(1.0);
226  setMuteUpdateEvents(true);
227 }
228 
229 std::shared_ptr<DeviceClient>
230 OpenHapticDeviceManager::makeDeviceClient(std::string name)
231 {
232  if (getInit())
233  {
234  LOG(WARNING) << "Can't add device client after initialization.";
235  return nullptr;
236  }
237  return m_impl->makeDeviceClient(name);
238 }
239 
240 bool
241 OpenHapticDeviceManager::initModule()
242 {
243  if (getInit())
244  {
245  LOG(WARNING) << "OpenHapticDeviceManager already initialized. Reinitialization not implemented.";
246  return false;
247  }
248  return m_impl->init();
249 }
250 
251 void
252 OpenHapticDeviceManager::updateModule()
253 {
254  m_impl->update();
255 }
256 
257 void
258 OpenHapticDeviceManager::uninitModule()
259 {
260  m_impl->uninit();
261 }
262 } // namespace imstk
void unlock()
End a thread-safe region.
Definition: imstkSpinLock.h:53
Vec3d m_angularVelocity
Angular velocity of the end effector.
Vec3d getForce()
Get/Set the device force.
ParallelUtils::SpinLock m_dataLock
Used for button and analog data.
Compound Geometry.
ParallelUtils::SpinLock m_transformLock
Used for devices filling data from other threads.
Quatd m_orientation
Orientation of the end effector.
void lock()
Start a thread-safe region, where only one thread can execute at a time until a call to the unlock fu...
Definition: imstkSpinLock.h:45
Vec3d m_velocity
Linear velocity of end effector.
Vec3d m_position
Position of end effector.
Subclass of DeviceClient for phantom omni Holds and updates the data sync or on its own thread Holder...